feat(ansible): ADR-0024 — one FreeIPA replaces OpenLDAP, and the ipa_structure role that implements the IPA side #318
No reviewers
Labels
No labels
bug
enhancement
in-progress
needs-info
needs-triage
p0
ready-for-agent
ready-for-human
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/nixos!318
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/316"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #316
Two deliverables: ADR-0024 recording the decision, and the
ipa_structurerole so that aprovision.ymlrun leavesipa.cloonar.comfully structured. No NixOS consumer changes — those are #317.ADR-0024
docs/adr/0024-one-freeipa-replaces-openldap.md. One FreeIPA replaces OpenLDAP entirely; a mail domain becomes themailattribute plus a group rather than anolcDatabasesuffix. It reverses the FreeIPA rejections in ADR-0022 and ADR-0023 one at a time, and the two that actually needed arguing are:catchAllAclendsby * readwith noolcRequires: authc(#303). FreeIPA could not represent a separation the directory was not making.ipa migrate-dsdoes not dropuserPassword, andipa-pwd-extop'sPRE_BINDhook mints the Kerberos key on the first successful simple bind./ipa/migrationdoes nothing but perform that bind, so Dovecot withauth_bind = yesmigrates people as they collect their mail — and decision 4 choseauth_bind = yesanyway, because IPA lets no bind readuserPassword.ADR-0023's
{CRYPT}!partition and per-user cutover are recorded as corrected rather than replaced, since neither was implemented and the premise behind them is false for FreeIPA.ansible/roles/ipa_structureRuns between
freeipaandverify.defaults/main.ymlholds the whole shape; the task files are mechanical over it.cloonarAccountwithcloonarMailQuota,cloonarCloudQuota,cloonarMailSendOnly, as aschema.ddrop-in merged byipa-ldap-updater --schema-file, then merged into IPA's default user objectclasses via a read-union-write onipaconfig.customizationphase plus theipaserverparameters it needs. Both are required: the UI form is a static spec, anduser_modrejects any option with no declared param.adm-*sudo groups, both hostgroups. Two passes so nesting never has to create a group; only the nested-group list is synced, so a person added by hand is left alone.allow_alldisabled,hbac-admins-allin its place.cn=sysaccounts,cn=etc, plus the permission without which a custom attribute is invisible to them.verify— seven new checks: schema present, objectclass defaulted, every group present with the intended POSIX state, memberOf flattened across every nesting edge,allow_alldisabled, hostgroups present, and each sysaccount binding and searching.Four findings that changed the design mid-flight
mailQuotais taken. Stock 389DS ships one (2.16.840.1.113730.3.1.21,50ns-mail.ldif), and two attributeTypes sharing a NAME is a schema error. Everything iscloonar-prefixed. OIDs sit under2.25.<uuid>, the ITU-T UUID arc — globally unique without an IANA PEN.5 GiB(which Nextcloud reads as five bytes), but Nextcloud's quota field also takesnoneanddefault. Thepatternon each IPA parameter recovers the check at the only place a quota is written.ipagroupcreates POSIX by default and FreeIPA never demotes, sononposixis passed explicitly on every group.adminsis IPA's own and already POSIX; the defaults record that rather than trying to demote it, which would have failed the play permanently.ipasysaccountneeds FreeIPA ≥ 4.13 andbasegates the IdM stack out of unattended updates, so the box stays on 4.12. The binds are plain LDAP entries created overldapi:///, where root's peercred maps to Directory Manager and no password crosses a network.Verification
scripts/ansible-checkpasses —0 failure(s), 0 warning(s) in 57 files, profile production. Beyond that, offline:templatemodule and parsed with an independent RFC 4512 parser;SINGLE-VALUEplacement,MAY ( a $ b $ c )spacing and the2.25.<big int>.NOID form all check out, and the OID was re-derived from its UUIDesprima; the rendered Python compiles and ispyflakes-cleanverifyLDIF parsing was exercised against generated fixtures — good input passes, and a promoted-to-POSIX group, an unflattened memberOf, an enabledallow_all, a missing hostgroup and dropped-o ldif-wrap=noeach fail itNothing here has run against the live IPA. The playbook has not been executed; #315 is still open.
Operator action before the next run
The per-server sops file needs a new key — the template is updated:
Known gap
ansible/playbooks/accounts.ymlalready createsemployees/financedelegation teams that hold people directly, which is layer-1 behaviour from groups outside layer 1, andemployeesoverlapsstaff. Left alone deliberately — it is a shipped mechanism and reconciling it belongs with the mail cutover in #317. The ADR records it rather than papering over it.ADR-0024 records the decision to replace OpenLDAP and mail's standalone KDC with the one FreeIPA, and reverses the FreeIPA rejections in ADR-0022 and ADR-0023 point by point. A mail domain becomes the `mail` attribute plus a group rather than an `olcDatabase` suffix; the unimplemented parts of ADR-0023 — the `{CRYPT}!` partition and the per-user cutover — are corrected rather than merely superseded, because `ipa-pwd-extop` mints a Kerberos key on the first successful simple bind and that is the silent path ADR-0023 concluded did not exist. The `ipa_structure` role implements the IPA side, between `freeipa` and `verify` in provision.yml: - the `cloonarAccount` auxiliary objectclass and its three attributes, as a `schema.d` drop-in merged by `ipa-ldap-updater`, plus a merge into IPA's default user objectclasses - an IPA Web UI plugin and the server-side parameters it needs, so the quota fields are editable on the user page - the five group layers, per-host `adm-*` sudo groups, both hostgroups, and the nesting that makes layer 1 the only place a person is ever put - `allow_all` disabled and `hbac-admins-all` in its place - four read-only bind accounts under `cn=sysaccounts,cn=etc` and the permission without which a custom attribute is invisible to them - seven new acceptance checks in `verify` Every attribute carries a `cloonar` prefix because stock 389DS already ships a `mailQuota`. No NixOS consumer changes; those are #317.[autoland] verdict: pass
PASS. Validated for landing.
scripts/ansible-check→ 0 failure(s), 0 warning(s) in 57 files, profile production (ansible-core 2.21.2, ansible-lint 26.6.0, collections pinned peransible/requirements.yml).Closes #316present, headafk/316sits directly onorigin/main(no conflict possible).ipa_structurerole, verify extensions, no NixOS consumer changes.Operator question resolved (sysaccount passwords): they are done in sops, exactly per the security invariant. The
.sops.yaml.exampleis only a plaintext key template; real values go into the encryptedansible/inventory/host_vars/ipa-cloonar.sops.yaml, whichprovision.ymldecrypts on the control node at runtime viacommunity.sops.load_vars. The committed encrypted file does not yet carryipa_sysaccount_passwords(sops leaves key names readable — only the four original keys are present), so the operator must add the map before the next provisioning run; until then the play fails loudly at theipa_structuresysaccounts task. That is the PR body's 'Operator action before the next run' and is by design.