Skip to content

certificate: enroll against AD CS natively over LDAP and MS-ICPR - #1449

Open
denisonbarbosa wants to merge 9 commits into
mainfrom
split/ldap-enrollment-core
Open

certificate: enroll against AD CS natively over LDAP and MS-ICPR#1449
denisonbarbosa wants to merge 9 commits into
mainfrom
split/ldap-enrollment-core

Conversation

@denisonbarbosa

@denisonbarbosa denisonbarbosa commented Aug 10, 2026

Copy link
Copy Markdown
Member

Certificate enrollment has been a point of struggle for adsys' users for a while now. Although it works, configuring CEPCES servers on Windows is not easy and comes with many pitfalls that can block the enrollment process. Also, CEPCES was designed to allow enrollment for machines outside of the domain, which is not the case for adsys.

This adds a native Go enrollment backend that talks to AD CS directly — LDAP for discovery, MS-ICPR over RPC for issuance — so autoenrollment needs no setup beyond the AD CS service itself. The legacy CEPCES path is kept intact and selectable.

UDENG-11047

This is part 2 of 4, split out of #1401.
#1448 (sudoers)this PRcertificate management API/CLIpackaging + e2e.
Depends on #1448 only for golden-file ordering.

What this contains

The certificate engine: everything under internal/policies/certificate/, plus the configuration plumbing that selects a backend. No new user-facing commands — those come in the next PR.

  • Machine bind to AD over LDAP with Kerberos/GSSAPI, StartTLS, and channel binding.
  • CA and template discovery from the configuration naming context, honouring template ACLs and the AutoEnroll right.
  • CSR submission over MS-ICPR, including manager-approval/pending requests and retry.
  • Enrollment state in JSON (replacing the Samba TDB cache), CA chain installation into the system trust store, and in-daemon renewal.
  • New certificate_enrollment setting (ldap | cepces), validated at every layer.

The code default stays cepces, so this is behaviourally inert for every existing installation. Fresh installs opt into ldap in the packaging PR at the end of the stack.

Security notes

  • Bootstrap trust. StartTLS validates the DC certificate against the system store plus adsys-managed CAs before the GSSAPI bind, and channel binding is enforced. A discovered self-signed AD CS root is trust-on-first-use, resting on that authenticated channel — the same assumption Windows autoenrollment makes about the directory. A non-self-signed CA must chain to an already-trusted anchor.
  • Chain binding. Enrollments are bound to the exact trust chain they were issued under; a CA whose root fails to install or verify is skipped with prior state preserved.
  • Atomicity. Key and certificate are renewed as a pair, written through O_EXCL temp files and renamed. Reconciliation is crash-safe, and a failed renewal keeps the still-valid certificate rather than letting orphan cleanup delete it.
  • ADSYS_TESTS_WITHOUT_KERBEROS is gated behind testing.Testing() and cannot take effect in production builds.

Notes for reviewers

  • CertAutoEnrollScript and adsysctl policy cert-autoenroll-script are unchanged — both files are byte-identical to main.
  • Suggested reading order: internal/constsldap.go/gssapi.goaccess.go/template.goenroll.gostate.go/lifecycle.go/pending.gocertificate.go.
  • The integration-harness commit is separate: it fixes the suite for NSS-provided users and splits the certmonger/CEPCES scenarios into their own goldens.

UDENG-11047

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds native AD CS certificate enrollment through LDAP/GSSAPI and MS-ICPR while preserving CEPCES compatibility.

Changes:

  • Adds certificate discovery, ACL evaluation, enrollment, renewal, state, and trust management.
  • Plumbs backend selection through daemon and policy-manager configuration.
  • Expands certificate documentation, dependencies, and automated coverage.

Reviewed changes

Copilot reviewed 78 out of 117 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
cmd/adsysd/daemon/daemon.go Adds enrollment configuration.
cmd/adsysd/integration_tests/adsys_test.go Extends integration setup.
cmd/adsysd/integration_tests/adsysctl_policy_test.go Splits certificate scenarios.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/apparmor.d/adsys/machine/nested/usr.bin.baz Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/apparmor.d/adsys/machine/usr.bin.bar Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/apparmor.d/adsys/machine/usr.bin.foo Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/apparmor.d/adsys/users/adsystestuser@example.com Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/dconf/db/gdm.d/adsys Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/dconf/db/gdm.d/locks/adsys Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/dconf/db/machine.d/adsys Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/dconf/db/machine.d/locks/adsys Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/dconf/profile/gdm Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/lib/private Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/lib/samba/cert_gpo_state_HOST.tdb Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/polkit-1/localauthority.conf.d/.empty Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/polkit-1/rules.d/00-adsys-privilege-enforcement.rules Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/.ready Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/empty-subfolder/.empty Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/final-machine-script.sh Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/other-script-user-logon Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/otherfolder/script-user-logoff Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/script-machine-shutdown Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/script-machine-startup Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/script-user-logon Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/subfolder/other-script Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/unreferenced-data Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/unreferenced-script Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/startup Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/users/.empty Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/sudoers.d/99-adsys-privilege-enforcement Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/systemd/system/.empty Updates CEPCES golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/apparmor.d/adsys/machine/nested/usr.bin.baz Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/apparmor.d/adsys/machine/usr.bin.bar Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/apparmor.d/adsys/machine/usr.bin.foo Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/apparmor.d/adsys/users/adsystestuser@example.com Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/dconf/db/gdm.d/adsys Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/dconf/db/gdm.d/locks/adsys Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/dconf/db/machine.d/adsys Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/dconf/db/machine.d/locks/adsys Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/dconf/profile/gdm Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/lib/private Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/lib/samba/cert_gpo_state_HOST.tdb Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/polkit-1/localauthority.conf.d/.empty Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/polkit-1/rules.d/00-adsys-privilege-enforcement.rules Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/.ready Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/empty-subfolder/.empty Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/final-machine-script.sh Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/other-script-user-logon Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/otherfolder/script-user-logoff Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/script-machine-shutdown Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/script-machine-startup Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/script-user-logon Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/subfolder/other-script Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/unreferenced-data Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/unreferenced-script Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/startup Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/users/.empty Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/sudoers.d/99-adsys-privilege-enforcement Updates certmonger golden.
cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/systemd/system/.empty Updates certmonger golden.
conf.example/adsys.yaml Documents backend setting.
docs/.custom_wordlist.txt Adds certificate terminology.
docs/explanation/certificates.md Explains native enrollment.
docs/how-to/certificates/configure.md Documents configuration.
docs/how-to/certificates/index.md Adds certificate navigation.
docs/how-to/certificates/setup.md Documents AD CS setup.
docs/how-to/certificates/troubleshoot.md Adds troubleshooting guidance.
docs/how-to/certificates/vpn.md Documents VPN usage.
docs/index.md Links certificate documentation.
docs/reference/adsysd-cli.md Updates daemon reference.
docs/reference/external-links.md Adds external references.
go.mod Adds LDAP/RPC dependencies.
go.sum Records dependency checksums.
internal/adsysservice/adsysservice.go Plumbs backend selection.
internal/consts/consts.go Defines enrollment constants.
internal/policies/certificate/access.go Evaluates template ACLs.
internal/policies/certificate/access_test.go Tests ACL evaluation.
internal/policies/certificate/binding.go Validates artifact ownership.
internal/policies/certificate/cert-autoenroll Retains legacy CEPCES helper.
internal/policies/certificate/cert-autoenroll_test.go Tests legacy helper behavior.
internal/policies/certificate/certificate.go Orchestrates enrollment lifecycle.
internal/policies/certificate/certificate_test.go Tests policy enrollment.
internal/policies/certificate/chain.go Discovers and verifies CA chains.
internal/policies/certificate/chain_test.go Tests chain handling.
internal/policies/certificate/directory.go Models directory discovery.
internal/policies/certificate/enroll.go Processes issued certificates.
internal/policies/certificate/enroll_test.go Tests issuance processing.
internal/policies/certificate/generation.go Publishes key/certificate pairs.
internal/policies/certificate/generation_reconcile.go Reconciles interrupted publications.
internal/policies/certificate/generation_test.go Tests atomic publication.
internal/policies/certificate/gssapi.go Implements GSSAPI binding.
internal/policies/certificate/gssapi_test.go Tests GSSAPI tokens.
internal/policies/certificate/kdc_dialer.go Adds context-aware KDC I/O.
internal/policies/certificate/kdc_dialer_test.go Tests KDC cancellation.
internal/policies/certificate/krb5_config.go Configures Kerberos discovery.
internal/policies/certificate/ldap.go Implements LDAP discovery.
internal/policies/certificate/ldap_test.go Tests LDAP and bootstrap trust.
internal/policies/certificate/lifecycle.go Handles issuance and polling.
internal/policies/certificate/lifecycle_test.go Tests enrollment lifecycle.
internal/policies/certificate/manage.go Adds management operations.
internal/policies/certificate/manage_test.go Tests management operations.
internal/policies/certificate/options_test.go Tests backend normalization.
internal/policies/certificate/pending.go Persists pending requests.
internal/policies/certificate/request.go Implements MS-ICPR requests.
internal/policies/certificate/state.go Stores enrollment state.
internal/policies/certificate/state_binding_test.go Tests state ownership.
internal/policies/certificate/template.go Selects eligible templates.
internal/policies/certificate/template_test.go Tests template eligibility.
internal/policies/certificate/testdata/TestApplyPolicyCEPCES/golden/computer,_configured_to_enroll Updates CEPCES golden.
internal/policies/certificate/testdata/TestApplyPolicyCEPCES/golden/computer,_configured_to_enroll,_advanced_configuration Updates CEPCES golden.
internal/policies/certificate/testdata/TestApplyPolicyCEPCES/golden/computer,_configured_to_unenroll Updates CEPCES golden.
internal/policies/certificate/testdata/TestApplyPolicyCEPCES/golden/computer,_no_entries,_samba_cache_present Updates CEPCES golden.
internal/policies/certificate/testdata/TestCertAutoenrollScript/golden/unenroll_with_cepces_not_installed Adds missing-CEPCES golden.
internal/policies/certificate/testdata/TestCertAutoenrollScript/golden/unenroll_with_certmonger_not_installed Adds missing-certmonger golden.
internal/policies/certificate/trust.go Installs CA trust artifacts.
internal/policies/certificate/trust_test.go Tests trust transactions.
internal/policies/manager.go Integrates certificate manager.
internal/policies/manager_test.go Tests manager integration.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/policies/certificate/template.go Outdated
Comment thread internal/policies/certificate/certificate.go Outdated
Comment thread internal/policies/certificate/certificate.go Outdated
Comment thread internal/policies/certificate/certificate.go
Comment thread internal/policies/certificate/access.go Outdated
Comment thread docs/how-to/certificates/configure.md Outdated
Comment thread internal/policies/certificate/ldap.go Outdated
Comment thread internal/policies/certificate/manage.go
Comment thread internal/policies/certificate/manage.go Outdated
Comment thread internal/policies/certificate/certificate.go Outdated

@adombeck adombeck left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll wait until the Copilot comments were addressed before reviewing this. Please request a review again when that's done. Thanks!

@denisonbarbosa

Copy link
Copy Markdown
Member Author

All Copilot comments are addressed, in five commits on top of the branch:

  • certificate: let AD decide which templates the machine may enroll — accept CT_FLAG_PEND_ALL_REQUESTS (manager approval) and expand GENERIC_ALL through the DS generic mapping before evaluating a template ACE.
  • certificate: bootstrap trust only against CAs adsys installed — an unrelated local CA in the shared trust directory no longer counts as adsys-managed trust and no longer blocks the first enrollment.
  • certificate: keep management reads off the enrollment locks — verification releases the locks before any CRL fetch, discovery dials before taking them (raised on certificate: expose enrolled certificates over gRPC and adsysctl #1450, the code lives here).
  • certificate: stop reporting success over failures we dropped — trust store refresh failures are reported instead of logged, the legacy Samba cache is kept when certmonger cannot retire the CEPCES enrollment, and New rejects an unknown enrollment method.
  • docs: describe only the behaviour this change ships — no more claims about the packaging default or about commands that land later; the generated daemon CLI reference is dropped since it is refreshed after merge.

@adombeck ready for your review.

Base automatically changed from split/privilege-sudoers to main August 11, 2026 11:50
@denisonbarbosa
denisonbarbosa force-pushed the split/ldap-enrollment-core branch 3 times, most recently from d6d6d3d to 27cef91 Compare August 12, 2026 11:06
denisonbarbosa and others added 9 commits August 17, 2026 07:42
Certificate autoenrollment so far relied on CEPCES, a Python service that
has to be set up and maintained on the Windows side. That setup is easy
to get wrong, and CEPCES exists to serve machines that are not domain
members - which is never the case for adsys.

Enroll natively instead. The machine binds to Active Directory over LDAP
using its own Kerberos credentials, discovers the enrollment services,
CAs and templates straight from the configuration naming context, and
submits CSRs to the CA over MS-ICPR. Nothing beyond a working AD CS
deployment is required from the administrator.

The backend is selected by the new certificate_enrollment setting. It
defaults to cepces so existing installations keep their current
behaviour; only an explicit opt-in switches a machine to the native
path.

The native path owns the material it issues: enrollment state is kept in
JSON next to the certificates, CA chains are installed into the system
trust store, keys and certificates are renewed as a pair through
O_EXCL temporary files, and manager-approval requests are tracked as
pending enrollments until the CA issues or denies them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The policy update integration tests could not run unattended anymore.
The mock containers resolve the connecting UID through the local passwd
and group files only, so a developer whose account comes from LDAP or
SSSD was unresolvable inside the container, and the certmonger and
CEPCES scenarios shared one golden tree even though the two binaries
fail independently.

Copy the host NSS databases into the container and append the current
user and group when the local files do not already provide them, split
the certmonger and CEPCES scenarios into their own goldens, and isolate
the dependency PATH so the suite no longer picks up host binaries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Describe the certificate_enrollment setting and what each backend
requires, so administrators can tell which path a machine is on and what
the native one expects from the domain.

The setup guide now states the network prerequisites for the LDAP
method - StartTLS to the domain controllers and the CA's RPC endpoint -
the troubleshooting guide covers the native path's failure modes rather
than only certmonger's, and the CEP/CES external links section goes away
with the requirement to configure those services.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two eligibility checks disagreed with what Active Directory itself
computes, so machines silently enrolled fewer certificates than the
domain grants them.

Templates carrying CT_FLAG_PEND_ALL_REQUESTS were rejected outright, yet
that flag is the ordinary "CA certificate manager approval" setting and
the enrollment lifecycle already persists and polls a pending
disposition until an operator approves it. Rejecting the flag made the
advertised manager-approval support unreachable.

An ACE granting GENERIC_ALL was read as granting no control access at
all. Windows expands generic rights through the object's generic
mapping before the access check, and for directory objects that mapping
contains ADS_RIGHT_DS_CONTROL_ACCESS, so a template whose applicable ACE
grants (or denies) GenericAll was evaluated the wrong way round.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A machine that had any local CA certificate in the global trust
directory could never complete its first native enrollment: the
directory is shared with the administrator, but every parseable
certificate in it counted as adsys-managed trust, which is precisely the
condition that disables the first-enrollment bootstrap. The domain
controller certificate then had no way to chain to a trusted root and
StartTLS verification failed.

Attribute only the artifacts adsys installs, recognized by the on-disk
name that installCAChain generates. Unrelated roots still join the
verification pool, so an administrator-installed CA that legitimately
signs the DC certificate keeps working; it just no longer stands in for
the AD CS root that adsys has not installed yet.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Certificate verification and CA discovery are available to every local
caller, and both held the manager and trust lifecycle locks for the
whole of their network sequence: one CRL fetch per certificate, up to
ten seconds each, or a full LDAP discovery round. Repeatedly asking for
them was enough to keep policy application and every other certificate
operation waiting.

Verification now reads the state and the on-disk material under the
locks and releases them before contacting a CRL distribution point, and
discovery dials the domain controllers before taking the locks it needs
to cross-reference the local state.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Several failures were swallowed on paths that had already committed
enrollment state, so a machine could be told everything was fine while
the system disagreed.

A failing update-ca-certificates was only logged. The certificates and
their CA chain are on disk and recorded in the state at that point, but
the generated system bundle is stale, so consumers still cannot trust
the CA that was just installed - or still trust one that was just
removed. The refresh is now part of the reported result of enrollment,
unenrollment, renewal and removal. It does not roll back the
enrollment: a valid certificate must not be discarded because a bundle
refresh failed, and the next refresh retries.

Retiring a legacy CEPCES enrollment deleted the Samba cache even when
the helper had returned early because certmonger is not installed. That
cache is the only record of the legacy certificates, keys and trust
store entries, so deleting it stranded them permanently. It is kept
instead, and the retire is retried on the next refresh; unenrollment
goes through the same helper so it cannot delete the cache behind its
back either.

WithEnrollmentMethod silently kept the default when handed a value that
is neither "ldap" nor "cepces", so a caller asking for a backend could
get the other one. Option failures are now collected and returned by
New.

The trust store refresh is injectable so the tests, which run
unprivileged, do not depend on the system command.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The configuration sample and the certificate guides announced that new
installations default to the native LDAP backend and documented the
adsysctl certificate commands. Neither is true yet: the code default is
cepces, the packaging change that writes the ldap default and the
management CLI both come later in this series, so a reader following
this documentation would set expectations the release does not meet, or
run a command that does not exist.

Document the code default, and describe how to inspect natively enrolled
certificates with what is available today: their layout on disk.

The generated daemon CLI reference goes away with it. It is refreshed
automatically once merged, so carrying it here only adds unrelated drift
- an option documented on main long before this series - to the diff.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The reference is produced by go generate ./cmd/adsysd and is not
regenerated anywhere after merge, despite what CONTRIBUTING.md still
claims: the automation that used to do it stopped running in 2023. A
change adding a flag therefore has to carry the regenerated file itself
or the published reference silently omits it.

Document --certificate-enrollment, added by this series. The bulk of
the diff is the column realignment that the longer flag name forces on
every option block.

This also picks up --gpo-list-timeout, which landed on main without a
refresh and would otherwise stay missing until something else happened
to regenerate the file.
@denisonbarbosa
denisonbarbosa force-pushed the split/ldap-enrollment-core branch from be18dd8 to 16e6149 Compare August 17, 2026 11:42
Comment thread docs/how-to/certificates/configure.md Outdated
Comment on lines +23 to +24
# Native Go implementation (LDAP/RPC) — default for new installations
certificate_enrollment: ldap

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it relevant information for users that the implementation is native Go?

Comment thread docs/how-to/certificates/configure.md Outdated
Comment on lines +26 to +27
# Legacy Python/CEPCES implementation — default for existing installations
# certificate_enrollment: cepces

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise, I'm skeptical if it's relevant that this is implemented in Python. Making clear that it's not the recommended method and mainly there for backwards compatibility ("legacy") makes sense to me though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants