certificates: Allow LDAP enrollment - #1401
Conversation
de6f2bd to
e7ef5e8
Compare
e7ef5e8 to
b216827
Compare
There was a problem hiding this comment.
Pull request overview
This PR introduces a new native (Go) certificate auto-enrollment path that discovers AD CS enrollment data via LDAP and submits CSRs via MS-ICPR (RPC), while keeping the existing CEPCES/Python path for backward compatibility and wiring enrollment-method selection through daemon/service configuration and packaging/docs.
Changes:
- Add LDAP discovery, Kerberos GSSAPI bind support, MS-ICPR CSR submission, and on-disk enrollment state/trust-store management in the certificate policy manager.
- Add configuration plumbing (
certificate_enrollment) across daemon/service/policy manager, plus updated tests and integration fixtures. - Update docs and Debian packaging to reflect the new enrollment method options and remove legacy script-dump flows.
Reviewed changes
Copilot reviewed 64 out of 99 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/policies/manager.go | Plumbs certificate manager injection and enrollment method into policy manager construction. |
| internal/policies/manager_test.go | Updates policy-manager tests to use the new certificate manager wiring and LDAP enrollment mocks. |
| internal/policies/certificate/trust.go | Adds root CA installation/symlink creation and trust-store update helpers. |
| internal/policies/certificate/state.go | Adds JSON state persistence for enrollment (replacing Samba TDB cache). |
| internal/policies/certificate/ldap.go | Implements LDAP discovery and Kerberos (GSSAPI) bind support and ccache lookup. |
| internal/policies/certificate/ldap_test.go | Adds unit tests for LDAP discovery/template lookup helpers. |
| internal/policies/certificate/gssapi.go | Adds a Go LDAP GSSAPI client implementation for Kerberos binds. |
| internal/policies/certificate/gssapi_test.go | Adds unit tests for GSSAPI token unwrap/wrap behavior and RRC handling. |
| internal/policies/certificate/enroll.go | Implements MS-ICPR CSR submission and key/CSR generation helpers. |
| internal/policies/certificate/enroll_test.go | Adds tests for RPC credential creation from Kerberos ccaches. |
| internal/policies/certificate/certificate.go | Introduces dual enrollment methods (ldap/cepces) and LDAP enrollment flow (state + cert/key writing). |
| internal/policies/certificate/certificate_test.go | Reworks certificate policy tests to exercise LDAP enrollment path and state cleanup. |
| internal/policies/certificate/testdata/TestApplyPolicy/golden/computer,_no_entries,_samba_cache_present | Removes golden output tied to legacy Python autoenroll script behavior. |
| internal/policies/certificate/testdata/TestApplyPolicy/golden/computer,_configured_to_unenroll | Removes golden output tied to legacy Python autoenroll script behavior. |
| internal/policies/certificate/testdata/TestApplyPolicy/golden/computer,_configured_to_enroll,_advanced_configuration | Removes golden output tied to legacy Python autoenroll script behavior. |
| internal/policies/certificate/testdata/TestApplyPolicy/golden/computer,_configured_to_enroll | Removes golden output tied to legacy Python autoenroll script behavior. |
| internal/consts/consts.go | Adds constants for enrollment method selection and defaults. |
| internal/adsysservice/policy.go | Changes CertAutoEnrollScript RPC response to informational text (no longer returns embedded script). |
| internal/adsysservice/adsysservice.go | Adds service option to pass certificate_enrollment into the policy manager. |
| internal/ad/ad.go | Adds test-only env var to disable Kerberos in integration environments without a KDC. |
| cmd/adsysd/daemon/daemon.go | Adds CLI/config flag plumbing for certificate_enrollment. |
| cmd/adsysd/client/policy.go | Removes CLI command that dumped the legacy cert-autoenroll script. |
| cmd/adsys-certsubmit/main.go | Adds a new helper binary intended for certmonger-style operations (SUBMIT/IDENTIFY/GET-SUPPORTED-TEMPLATES). |
| cmd/adsysd/integration_tests/adsysctl_policy_test.go | Splits certmonger/cepces missing-binary scenarios and removes script-dump tests; improves NSS wrapper handling. |
| cmd/adsysd/integration_tests/adsys_test.go | Ensures integration tests run with Kerberos disabled and augments passwd/group for container mocks. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/systemd/system/.empty | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/users/.empty | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/.ready | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/startup | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/empty-subfolder/.empty | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/final-machine-script.sh | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/other-script-user-logon | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/otherfolder/script-user-logoff | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/script-machine-shutdown | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/script-machine-startup | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/script-user-logon | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/subfolder/other-script | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/unreferenced-data | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/run/machine/scripts/scripts/unreferenced-script | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/sudoers.d/99-adsys-privilege-enforcement | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/polkit-1/localauthority.conf.d/.empty | Updates golden fixtures for integration test scenario. |
| 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 golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/apparmor.d/adsys/machine/usr.bin.bar | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/apparmor.d/adsys/machine/usr.bin.foo | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/apparmor.d/adsys/machine/nested/usr.bin.baz | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/apparmor.d/adsys/users/adsystestuser@example.com | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/dconf/profile/gdm | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/dconf/db/gdm.d/adsys | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/dconf/db/gdm.d/locks/adsys | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/dconf/db/machine.d/adsys | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/dconf/db/machine.d/locks/adsys | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/lib/private | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_certmonger_is_not_available/lib/samba/cert_gpo_state_HOST.tdb | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/systemd/system/.empty | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/users/.empty | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/.ready | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/startup | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/empty-subfolder/.empty | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/final-machine-script.sh | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/other-script-user-logon | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/otherfolder/script-user-logoff | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/script-machine-shutdown | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/script-machine-startup | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/script-user-logon | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/subfolder/other-script | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/unreferenced-data | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/run/machine/scripts/scripts/unreferenced-script | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/sudoers.d/99-adsys-privilege-enforcement | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/polkit-1/localauthority.conf.d/.empty | Updates golden fixtures for integration test scenario. |
| 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 golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/apparmor.d/adsys/machine/usr.bin.bar | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/apparmor.d/adsys/machine/usr.bin.foo | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/apparmor.d/adsys/machine/nested/usr.bin.baz | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/apparmor.d/adsys/users/adsystestuser@example.com | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/dconf/profile/gdm | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/dconf/db/gdm.d/adsys | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/dconf/db/gdm.d/locks/adsys | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/dconf/db/machine.d/adsys | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/dconf/db/machine.d/locks/adsys | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/lib/private | Updates golden fixtures for integration test scenario. |
| cmd/adsysd/integration_tests/testdata/TestPolicyUpdate/golden/does_not_error_when_cepces_is_not_available/lib/samba/cert_gpo_state_HOST.tdb | Updates golden fixtures for integration test scenario. |
| e2e/cmd/run_tests/01_provision_client/main.go | Drops python3-cepces install in e2e provisioning. |
| docs/reference/external-links.md | Removes CEP/CES external links section and normalizes formatting. |
| docs/index.md | Adjusts formatting; still references CEP/CES link anchor (now removed elsewhere). |
| docs/how-to/certificates/setup.md | Updates package requirements and introduces method selection documentation. |
| docs/how-to/certificates/configure.md | Documents certificate_enrollment config and updates helper-location example. |
| docs/how-to/certificates/index.md | Removes CEP/CES mention from external resources list. |
| docs/how-to/certificates/troubleshoot.md | Updates troubleshooting guidance toward native LDAP/RPC flow and state files. |
| docs/explanation/certificates.md | Adds method overview/configuration details for LDAP vs CEPCES enrollment. |
| debian/rules | Removes vendored Python install step and adds (currently empty) test override target. |
| debian/control | Moves Python/Samba deps to Recommends and adjusts runtime deps. |
| debian/adsys.postinst | Creates default /etc/adsys.yaml for new installs setting LDAP enrollment. |
| debian/adsys.install | Stops installing legacy Python assets directory. |
| debian/adsys.apport | Removes python3-samba from apport related packages list. |
| debian/copyright | Removes legacy vendored Samba Python copyright stanza. |
| debian/changelog | Adds a test changelog entry for certificate changes. |
| conf.example/adsys.yaml | Documents the new certificate_enrollment configuration key. |
| go.mod | Adds new Go dependencies for LDAP/Kerberos/MSRPC. |
| go.sum | Records new module checksums for added dependencies. |
Comments suppressed due to low confidence (1)
debian/rules:88
- An empty override_dh_auto_test target disables the default test execution in the Debian build. If the intent is not to skip unit tests, call dh_auto_test here (integration tests are already skipped via ADSYS_SKIP_INTEGRATION_TESTS).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
b216827 to
ab3b09b
Compare
ab3b09b to
9f69032
Compare
|
Pushed follow-up commits addressing the review threads (now resolved). Summary of what changed:
Intentional/by design: the code-level default stays |
772d307 to
d3ffabf
Compare
a292570 to
cf28df7
Compare
29479ac to
07e7c73
Compare
07e7c73 to
84f892d
Compare
The native enrollment path has to talk to Active Directory's LDAP directory as the machine account, but go-ldap can only drive a GSSAPIClient and ships no Kerberos implementation of its own. Bridging the two lets us reuse the machine credentials already present in the host's credential cache instead of provisioning or storing a separate secret. The adapter performs the GSSAPI/SASL bind with the machine credentials, and LDAP discovery upgrades the connection with StartTLS before the bind so directory responses used for enrollment are protected in transit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The LDAP enrollment method must learn which enrollment services and certificate templates are available without shelling out to the Samba-based Python helper. Querying the directory directly makes discovery a first-class, testable part of the daemon rather than a side effect of an external process. The directory client sits behind an interface so tests can inject responses without a live AD, and connections reuse the machine's Kerberos cache so no extra credentials are needed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Dropping the Samba/CEPCES stack also removes the bookkeeping it provided: nothing records what has already been enrolled, and the CAs that AD advertises are not trusted by the system. Left unaddressed, every policy refresh would request brand new certificates and the issued certificates would fail to validate. Persisting enrollment state to disk lets repeated policy applications reuse the existing key and certificate pairs instead of re-issuing them, and installing the root CAs discovered over LDAP into the system trust store means both the issued certificates and the servers presenting them verify correctly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Requesting the certificate is the last step that still required cepces. Submitting the CSR straight to AD CS over the MS-ICPR RPC interface lets the entire enrollment run natively, authenticated with the machine's Kerberos credentials and with no Python runtime on the host. Submission is kept behind an interface so the network call stays out of unit tests, and the pending (manual-approval) result is handled explicitly rather than assuming the CA issues immediately. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Discover and deterministically validate complete AD CS chains, bind persisted enrollment state to domain and certificate fingerprints, and verify issued and reused key material against the selected issuer. Authorize published machine templates with a bounded LDAP lookup, safe DACL access checks, and a complete authenticated machine token before enrollment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Read tokenGroups with a base-object query after uniquely resolving the machine, accept signed AD bit flags, and distinguish trusted-root publication from issuer-only certificate sources. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Persist issuer and ordered chain bindings per template, retain chains across targeted renewals, verify management operations against only those bindings, and make trust publication rollback ownership-aware. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ge cases Separate the state envelope from per-leaf validity so a targeted or --all renewal can replace an expired, missing or mismatched target leaf, or a target whose old issuer chain expired, without first requiring every stored leaf to be currently valid. Untouched templates are still validated exactly before the CA is touched, a failed target keeps its old entry only when that entry validates, and target state is never migrated until renewal succeeds. Make trust and leaf artifact paths collision-resistant with a SHA-256 digest over the full unsanitized CA/object identity and certificate fingerprint, so distinct raw identities such as "Corp CA" and "Corp-CA" no longer share paths and one installation's rollback cannot delete another's committed artifacts. Route removal and unenroll deletions through removeUnreferencedPaths after the current state is persisted, honoring other object state files so shared cert/key/chain paths survive, and propagate cleanup errors. Return aggregated errors from trust installation rollback and join them at every call site, preserving path detail so leftovers are never reported as a clean rollback. Verify certificates against the identity derived from the requested object and domain, rejecting a state whose object name or identity does not match, which closes sanitizeName state-file collisions such as "host$" versus "host-". Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use raw-identity discriminators for state, leaf, and management identifiers; safely migrate legacy state and preserve cross-object ownership during renewal and removal. Serialize trust lifecycles across managers and propagate rollback and cleanup failures through enrollment results. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Model AD CS dispositions and persist exact pollable requests with secure key and CSR material. Verify and publish issued key/certificate pairs through a single crash-durable generation pointer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Make generation publication and cleanup directory-durable, reconcile interrupted markers against durable state, contain legacy artifact cleanup, and preserve pending polling cursors after committed removals. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Attempt independent pending draft cleanup after durable issued-state commits even when generation marker finalization fails. Preserve uncertain generation artifacts for restart reconciliation and propagate all cleanup failures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Switching certificate_enrollment from cepces to ldap dispatched straight to the native flow: it never ran the CEPCES unapply path, so certmonger kept tracking the requests and CAs registered under <state>/private/certs/<CA>.<template>.key and <state>/certs/<CA>.<template>.crt, remaining an active writer of files the native backend now manages. Run the legacy unenroll action before the first native enrollment (and before native unenrollment) whenever the Samba cache is present, so getcert remove-ca/stop-tracking retires the requests and the TDB is deleted only after the cleanup ran. The switch is transactional: a cleanup failure aborts the native enrollment and keeps the legacy state for the next refresh. When certmonger is not installed the script skips cleanup, so the leftover cache is removed directly as it cannot be an active writer. The helper previously skipped unenroll unless cepces-submit was also present, although only getcert is needed to stop tracking; unenroll now requires just certmonger. Also restore the CEPCES ApplyPolicy coverage dropped earlier in the series (golden output is byte-identical to the pre-LDAP suite) and add backend-switch regression tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The CertTemplates RPC forwarded the caller-provided server unchanged into the LDAP dialer while holding the global certificate mutex. Any local user could point the daemon at an accepting-but-stalled endpoint (e.g. adsysctl certificate templates localhost:4444) and, because only connection establishment had a timeout, block certificate management and machine policy refreshes indefinitely. The requested server is now only a selector over the CAs discovered in the configured domain: discovery always runs against the domain's SRV candidates with context and per-candidate deadlines, so the daemon never dials an arbitrary address or port. The query is stateless and touches no enrollment state, so it takes neither the manager mutex nor the trust lifecycle lock; cancellation propagates from the caller. Authorization stays ActionAlwaysAllowed, matching the other read-only certificate RPCs (list, status, verify, CA info); the operation now shares their exact confinement and deadline properties. Mutating operations still require root with ActionPolicyUpdate. Remove the now-unused exported GetSupportedTemplates helpers that derived the discovery target from the caller string. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The fixed 30-day renewal window means a certificate with a seven-day lifetime is immediately considered due for renewal after issuance and re-enrolled on every policy refresh. Bound the window to a third of the certificate's own lifetime: a 6-day certificate is now reused until its last two days, while year-long certificates keep the existing 30-day behavior. deriveHealth uses the same bounded window so freshly issued short-lived certificates report healthy instead of due_renewal. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
deriveHealth only checked NotAfter, so a certificate whose NotBefore is in the future (issuance clock skew or a rolled-back CA clock) reported healthy with exit code 0, while certificate verify rejected the same certificate as outside its validity window. Add a distinct CERT_HEALTH_NOT_YET_VALID state (not_yet_valid, exit code 6) derived when the certificate is before its NotBefore, matching the verify path. The status command help, its generated reference, and the management documentation now also state that exit code 5 covers both key mismatch and unparseable results, and the renewal window description reflects the bounded window. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Every certificate reported the state-wide UpdatedAt as its enrollment time. Since state is also saved after a failed renewal and when an unrelated certificate is removed, the displayed last_enrolled moved for certificates that were never re-issued, which is misleading for audit and monitoring. Persist a per-template enrolled_at at successful issuance (both direct and pending-poll completion paths) and report it as last_enrolled, falling back to the state UpdatedAt for state written before the field existed. A failed renewal now keeps the original timestamp, removing one certificate no longer retimes the rest, and only a successful renewal advances it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The online revocation check called x509.ParseRevocationList and set RevocationChecked on any parseable response. A forged empty CRL — not signed by the issuing CA, or long stale — therefore produced "revoked: no", PASS, and exit 0. The fetched CRL is now only trusted when its issuer belongs to the certificate's persisted chain, the signature verifies, and it is inside its ThisUpdate/NextUpdate window (with a small clock-skew tolerance). Foreign-signed, expired, not-yet-valid, unparseable, and unreachable CRLs leave RevocationChecked false with an explanatory message, so the CLI hides the revoked verdict instead of reporting a clean "no". A non-OK HTTP status is also no longer fed to the parser. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bring the certificate management documentation in line with the implementation and make git diff --check clean again: - vpn.md names the enrolled certificates galacticcafe-CA.*, matching the CA in the example, instead of the nonexistent galactic-CA.*. - manage.md uses 2026/2027 dates so a healthy certificate does not show 2024 expiry with 210 days remaining. - The landing page links the management guide from its Certificates section, and the setup page states the network prerequisites for the LDAP method: StartTLS to the DCs on TCP 389 and the CA's RPC endpoint on TCP 135 plus dynamic RPC ports. - Regenerate the Jammy packaging patch with minimal context so its context lines carry no trailing whitespace or space-before-tab indentation; the result applied by build-deb.sh is byte-identical and the noble symlink follows it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The certificate assertion still ran getcert and expected certmonger's MONITORING state, so on a fresh install — which now defaults to the native LDAP method — it either failed outright or, on images where an older adsys was upgraded, silently kept covering only the legacy backend. Pin the enrollment method in /etc/adsys.yaml for each phase and exercise the flow end to end against the live AD CS: - native default: first enrollment, health, certificate/key correspondence, chain verification, and system trust via adsysctl certificate list/cas/verify, then a forced renewal - explicit CEPCES compatibility: install certmonger and python3-cepces, select the legacy backend, and keep the getcert MONITORING assertion - migration: switching back to LDAP retires the certmonger requests, so getcert no longer reports the template as monitored Assertions on asynchronous results poll instead of relying on a fixed sleep. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mark the migration helper process as test-controlled for gosec, drop the always-nil manager return from the timestamp fixture, and document why the shared mock helpers keep their now-constant parameters generic, so golangci-lint run ./... reports zero issues again. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
84f892d to
79fb2d2
Compare
The documentation spell check fails on the certificate management guide:
Vale's Canonical dictionary does not know "untrusted", which the CRL
section uses to describe a revocation list that cannot be tied back to
the certificate's issuer chain.
The word is correct and the surrounding paragraph reads worse without
it, so allow it in the custom word list next to the other negated forms
already listed there ("unescaped", "unparseable").
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Superseded by a split stack. Same final tree, rebuilt from scratch into reviewable pieces:
Why: this repo merges with merge commits only, so all 48 commits here would land on The split is verified: Closing in favour of the stack. Branch |
Split out of #1401 as an unrelated drive-by fix, so it can be reviewed and land on its own. The generated sudoers file ended with a trailing blank line, and every policy entry appended its own `\n` whether or not it produced content. Entries yielding an empty sudoers section emitted a stray newline, and the file always finished with one blank line more than needed. Sections are now collected and joined with a single blank-line separator, so they stay visually separated but the file ends with exactly one newline. Golden files updated accordingly.
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 PR 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
What changes
Native enrollment backend (
internal/policies/certificate/)AutoEnrollright.Backend selection
certificate_enrollmentsetting (ldap|cepces), plumbed through the daemon CLI, service options and the policy manager, and validated at every layer.cepces, so existing installations are unaffected. Fresh installs opt intoldapthroughpostinst.cepcestoldapis transactional: certmonger stops tracking the old material before the TDB is removed, so the two backends never manage the same files at once.Certificate management CLI — new
adsysctl certificatecommand group:list,status,renew,remove,verify(with--onlineCRL checking),cas, andtemplates. Backed by seven new gRPC methods and aCertHealthenum (healthy,due_renewal,expired,missing,key_mismatch,unparseable,not_yet_valid).Packaging
python3-cepcesmoves fromRecommendstoSuggests: adsys is inmainand cepces is still inuniverse, so aRecommendswould create a component mismatch without a MIR.ca-certificatesadded toDepends(the trust store is now written directly).Build-Dependsraised to Go 1.25 to matchgo.mod; the Jammy/Noble E2E backport patches were refreshed to match, with a new test that fails ifdebian/controlordebian/rulesdrift away from them again.postinstwrites/etc/adsys.yamlon first install only;postrmremoves it on purge. This is deliberately not conffile-managed — see the inline comment for the upgrade/reinstall reasoning.Security notes
The threat model got most of the review attention, so calling out the decisions explicitly:
O_EXCLtemp files and renamed. Reconciliation is crash-safe, and a failed renewal keeps the still-valid certificate instead of letting orphan cleanup delete it.verify --onlineonly trusts a CRL signed by the certificate's issuer chain and inside its validity window; anything else is reported as indeterminate rather than clean.templatesRPC holds no global lock, and the requested server is only a selector over domain-discovered CAs — it is never dialed.ADSYS_TESTS_WITHOUT_KERBEROSescape hatch is gated behindtesting.Testing()and cannot take effect in production builds.Testing
Unit and integration coverage for the new package, plus E2E scenarios that pin the backend and exercise LDAP enrollment (health, key match, chain, trust store, renewal), CEPCES compatibility, and the CEPCES→LDAP migration.
Notes for reviewers
CertAutoEnrollScriptandadsysctl policy cert-autoenroll-scriptare unchanged — an earlier revision altered them and that was reverted.adsys-certsubmithelper explored earlier was dropped rather than shipped.consts→ldap.go/gssapi.go→enroll.go→state.go/lifecycle.go→manage.go→ packaging.