Repository navigation
fix(freeipa,dns): leave out replicas FreeIPA does not list as DNS servers; one forwarder rule (follow-up to #73) - #92
Conversation
…vers; one forwarder rule Second review of #73 (head 5e48d67): - P2: a replica with freeipa_setup_dns unset counts as a DNS provider, but freeipa-server-replica is a day-2 playbook outside site.yml, so an inventory lists it before it is promoted; a site-wide deploy then failed at dns.md G5, and a consumer got a dead nameserver silently. New tasks/freeipa-dns-providers-live.yml asks FreeIPA itself (the zone's NS records, +norec) which servers run its DNS. A replica that leaves freeipa_setup_dns unset and is not listed is left out of the tier's stubs and the consumer's nameservers with a WARNING naming it and the two remedies; an explicit true stays in and fails closed at G5 or at the resolver. dns-apply.yml asks after dig is installed; the G5 and check-mode messages now name the replica case. The resolver installs dig first, asks the same, and then asks every nameserver it will write (FreeIPA +norec, tier and explicit servers recursively) for the FreeIPA server's FQDN before writing anything; one that does not answer fails the run with the list and the remedies, before any change. pilot_diagnose_dns_architecture follows the same NS records: such a replica is freeipa_replica_not_dns_server and the effective stubs and resolver lists leave it out. - P3: the primary and a replica now read forwarders identically (tasks/freeipa-dns-forwarders.yml picks ipa_dns_forwarders if defined, else freeipa_dns_forwarders if defined, else the caller's default: 8.8.8.8 on the primary, [] on a replica). An explicit empty value is no forwarders on both; before, the primary turned "", [] and null into 8.8.8.8. Every forwarder must be an IPv4/IPv6 address, checked before any install ("8.8.8.8,1.1.1.1", "1.1.1.1 port 53", {}, a number and an unclosed "[..." used to pass or crash). The Go model mirrors it; TestNormalizeForwardersMatchesPlaybookGate runs the task on 21 inputs and compares. - P3: stale "replicas default false / opt-in" comments and docs swept; freeipa_dns_server_ips documented as every server the tier stubs to; the dns runbook gate table covers the replica cases; the forwarder docs say exactly what both playbooks do. Co-Authored-By: Claude Opus 5.5 <[email protected]>
New record 2026-10-14-01a64f4.md: local checks; the topology test on four fresh VMs (L3/L4 failed=0, L5 pass, L6 changed=0); a replica listed before it is promoted (main fails at G5 and writes it as a nameserver; the candidate leaves it out with a warning, site.yml passes, an explicit true fails closed before any change; after promotion it is included and reruns are changed=0); the forwarder gate refusing bad values on the primary. docs/runbooks/dns.md §0.5 now describes this run's target and inventory. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
這是 #73(已合併為
驗證
詳見 這個 PR 和 #91 會改到相同的檔案,後合併的那個負責解衝突。 |
… are down, clear empty forwarders Second review of #92 (the #73 follow-up): - tasks/freeipa-dns-providers-live.yml: a replica whose own +norec NS answer is NOERROR+aa stays in, whatever name the inventory gives it; before, a replica installed under another name was left out because its inventory FQDN was not in FreeIPA's NS records. dig uses +tries=2, and "no FreeIPA server answered" is printed. The WARNING lists the four remedies. pilot_diagnose_dns_architecture follows the same rule (its provider probe also uses +tries=2). - tasks/freeipa-dns-client-nameserver-check.yml (new): the resolver asks every nameserver for the FreeIPA FQDN's A and AAAA, and fails only for a server that answers without an address, a FreeIPA server FreeIPA does not list that does not answer, or no working server. A server that is only down now (registered FreeIPA server, dns tier, explicit entry) gets a WARNING with a role-specific remedy and stays in. +norec only for FreeIPA servers; an explicit 127.0.0.1 is asked recursively. - tasks/freeipa-dns-forwarders.yml: forwarders are checked with Python's ipaddress on the controller (and net/netip in internal/diagnose); "<address> port <n>" is accepted (ipa dnsconfig-mod takes it, checked on FreeIPA 4.13.4), unspecified/multicast/zoned addresses are refused. The installers take plain addresses only, so when a run would call ipa-server-install, ipa-replica-install or ipa-dns-install with a port form, a pre_tasks gate stops it before any change. - freeipa-server-apply.yml: every run sets the realm's global forwarders (ipa dnsconfig-mod) and the primary's own forwarders (ipa dnsserver-mod <fqdn>, which its named prefers over the global ones) to the list; an explicitly empty list clears both, on an existing realm too. Before, an empty list skipped this, and a changed list never reached the server's install-time forwarders. freeipa-server-replica-apply.yml does the same for a replica's own forwarders (empty by default: it then uses the global ones). Breaking change, documented in freeipa-server.md v1.9, freeipa-server-replica.md v1.2 and group_vars/freeipa.example.yml. - dns-apply.yml prints the FreeIPA servers the stubs use and the ones left out; G5's message names named on a listed server. Tests from real pr92 captures: TestRegression_ResolverNameserverCheck (10 cases), TestDNSArchUnregisteredReplicas_KeepAReplicaThatAnswersItself, and 15 new forwarder cases in TestNormalizeForwardersMatchesPlaybookGate. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
第二輪 review 的問題已處理完,新 head review 項目 → 處理方式
自己的 adversarial review(
驗證(
另外觀察到:FreeIPA server 本機上,installer 的 |
Co-Authored-By: Claude Opus 5.5 <[email protected]>
…f answer, mapped addresses Review of 09ded7e: - pilot_diagnose_dns_architecture and its runbook still said pilot leaves each server's own forwarders alone; both playbooks now set them on every apply, so a mismatch means "re-apply", and the NotManaged entry is gone. - DNSArchUnregisteredReplicas counted a replica's own SOA answer too; tasks/freeipa-dns-providers-live.yml asks only NS, so the Go mirror now does the same. - An IPv4-mapped address is judged as the IPv4 address it maps on both sides (net/netip Unmap; Python's ipv4_mapped, which Python before 3.13 ignores for is_unspecified): ::ffff:0.0.0.0 and ::ffff:224.0.0.1 are refused by Go and by the playbook alike. - The resolver WARNING for a FreeIPA server that does not answer says whether FreeIPA lists it, or that no FreeIPA server answered. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The 5750f2a run created a new set of six VMs with `topology up`; the §0.5 line still described the 09ded7e run's `topology reset`. Docs only. Co-Authored-By: Claude Opus 5.5 <[email protected]>
kjelly
left a comment
There was a problem hiding this comment.
重新審查 head a2f8e6811594994a8ae44210b941ab0114302e06:未發現新增功能阻擋,但驗證摘要尚未符合 repo 必要欄位,轉為草稿。
[P3] 最新 candidate 的 evidence 缺少可追溯 run metadata。 docs/evidence/freeipa-replica-dns-provider/2026-10-15-5750f2a.md 表示另建六台 VM,但只有 OS/package 版本,沒有 target image digest;Run 也只寫日期,沒有 UTC 時段或正式 run ID。docs/actual-run-evidence.md §3 明定 run ID、UTC 時間、target image digest 與適用的外部 state fingerprint 為必要欄位。
請從原 run 可追溯資料補足並連回 tested candidate/tree;若無法復原,再實跑取得。成功 raw artifacts 在摘要提交後刪除符合規則,並非本次阻擋原因。
本次核對 latest execution blob prefixes 與 candidate 一致;internal/diagnose 全包與 resolver nameserver 真實 Ansible cases 通過。另以 merge-tree 確認 #91/#92 在 MCP、report、tests、runbook 有實際衝突。兩者目前都未合併;之後先合併任一個,另一個須保留雙方修正、解衝突並驗證新 candidate。
The review of #92 found that the records lack the target image digest, the UTC window and run IDs (docs/actual-run-evidence.md §3). Add them to the 01a64f4, 09ded7e and 5750f2a records, read after the run from records that outlive the VMs: libvirt's QEMU log of each domain (VM start, reset, rollback and destroy times; the base image each overlay used), the verify evidence store (run IDs) and the base image files (sha256; last modified before the runs). The diagnose calls' audit records were deleted with the run's scratch directory, and the records say so. No execution file changes. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
處理 2026-10-15 的 review:「驗證摘要缺少 image digest、UTC 時段及 run ID」。新 head
還原不了的部分:diagnose 呼叫的 audit record 存在 run 的 scratch 目錄,已經跟著刪除,所以這些呼叫沒有 run ID。record 裡寫明了這一點。 |
kjelly
left a comment
There was a problem hiding this comment.
Review of head fde3ce4: keep draft pending integration with main.
The new evidence metadata addresses the previous run-ID/image-digest finding. Focused DNS architecture/forwarder race tests and real Ansible resolver-nameserver cases pass on this head.
PR #91 has now merged as 306c444. GitHub reports this PR CONFLICTING, and git merge-tree confirms four content conflicts: cmd/pilot/cmd/mcp_diagnose_dns_architecture.go, docs/runbooks/dns-architecture-diagnose.md, internal/diagnose/dns_architecture_report.go, and internal/diagnose/dns_architecture_test.go.
Resolve these while preserving both PRs' fixes, then test the integrated candidate and update evidence for any changed execution paths. The separate-head test results do not validate the conflict resolution.
Conflicts, resolved by keeping both sides: - cmd/pilot/cmd/mcp_diagnose_dns_architecture.go: the tier's authority probes of left-out replicas are dropped (#92), and the internal-name skip detail is kept (#91); stringInList (#92) next to #91's name helpers. - internal/diagnose/dns_architecture_report.go: a left-out replica gives only freeipa_replica_not_dns_server (#92); an unprobed provider is skipped (#91). - internal/diagnose/dns_architecture_test.go: both sides' new tests. - docs/runbooks/dns-architecture-diagnose.md: #91's status and findings rows, plus freeipa_replica_not_dns_server. Semantic conflict: #91 compares every DNS provider's answer for the internal name with the reference provider's. A replica #92 leaves out is not a FreeIPA DNS server, so its expected failure became a false mismatch. The comparison now skips left-out replicas; TestBuildDNSArchDifferences_LeftOutReplicaIsNotComparedWithTheReference fails without the change. #91's TestBuildDNSArchStatus_UnknownConfigAndComparisonsAreNamed now expects #92's subject for server forwarders, "(ipa dnsserver-mod)". The diagnose runbook gets a §6 row for #92. Co-Authored-By: Claude Opus 5.5 <[email protected]>
New record 2026-10-15-287cf79.md: the merge's conflicts and the semantic fix, run from a clean checkout of the committed candidate on a new six-VM topology, with the run metadata captured during the run (run IDs, UTC window, backing chains and base image sha256, package versions, input digests). Topology test L3/L4 failed=0, L5 20/16/7, L6 changed=0, dns.md 16 pass + 4 not applicable; the targeted checks match 5750f2a's. Diagnose is complete 14/14 with only freeipa_replica_not_dns_server for the unpromoted replica; a control binary without the left-out skip gives partial with comparison_unknown for it. Local checks from the same checkout: go test -race 57 ok, golangci-lint 0, playbook-lint, spec lint. The dns and diagnose runbooks' §0.5 point at the record and say which cases were not re-run on 287cf79; both get changelog rows. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
#91 已合併進 合併
驗證(從已 commit 的
#91 的其他情境(tier 拒絕 client、FQDN 結尾的點、 詳見 |
The run metadata added to the 01a64f4, 09ded7e and 5750f2a records gave the base images' sha256 computed on 2026-10-15, reasoning from the files' mtimes that they were unchanged. A digest computed after a run does not show what that run used (review of #89), so the records now name only the base image from libvirt's log and say the digest was not recorded. 287cf79's record has the digests as captured during its run. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each targeted check's finishing time comes from the run script's log, captured during the run. Co-Authored-By: Claude Opus 5.5 <[email protected]>
New record 2026-10-15-287cf79-targeted.md repeats 09ded7e's targeted checks with their 44e2d20 controls on the current candidate and a new six-VM topology, with the metadata captured during the run (backing chains and base image sha256, packages, input digests, per-step UTC, verify run IDs, diagnose audit IDs): the replica under another inventory name, a listed replica's named stopped, forwarders cleared, port form and default on both lists, a replica's own forwarders (--tags R3), twelve forwarder gate values, and both install-time gates. Results match 09ded7e's; the candidate's diagnose also names the inventory/FreeIPA name mismatch for that replica's server forwarders (declared_live_absent). 09ded7e's record is marked superseded as evidence, and the dns runbook §0.5 points at the new record. Co-Authored-By: Claude Opus 5.5 <[email protected]>
New record 2026-10-15-287cf79-round1.md repeats 01a64f4's checks on the current candidate, with main f9fb701 as the control and the metadata captured during the runs: a replica listed before it is promoted (main fails G5 and writes it as a nameserver; the candidate leaves it out with a WARNING), site.yml on all six hosts, freeipa_setup_dns: true failing closed in dns-apply and in the consumer, promotion and reruns, and diagnose before and after. Topology A needed three attempts; the first two failed outside this PR (#110: concurrent replica DNS self-register; #111: guest clocks behind after rollback) and are recorded. Topology B (one primary, the unpromoted replica) covers the consumer cases that A's three-nameserver cap hides. 01a64f4's record is marked superseded; the dns runbook §0.5 points at the new record. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
依 #89 那邊 reviewer 的標準(不能用事後算的 hash 推定過去 run 的 image;所有引用為現行 evidence 的 record,都要有執行當下記錄的 §3 metadata)主動補強,新 head
|
…tative BuildDNSArchPaths printed a replica the playbooks leave out (freeipa_setup_dns unset and not listed by FreeIPA as a DNS server) as "FreeIPA DNS replica … authoritative for <domain>", because it checked only DNSProvider. It now prints that the replica is left out of the DNS paths. TestBuildDNSArchPaths_LeftOutReplicaIsNotAuthoritative fails without the change. Found by the review of 2f62058. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…b02b5cb The review of 2f62058 found that main had moved (#89, #101, #107) and changed files on this PR's tested path, so 287cf79's evidence did not cover the merged tree, and that the diagnose runbook still cited #91's 46fbc58 run for cases the merged code handles. New record 2026-10-15-b02b5cb.md repeats every check of the three 287cf79 records on b02b5cb (main merged + the left-out paths fix), with the 44e2d20 and main f9fb701 controls, plus #91's diagnose cases (tier refuses the client, trailing-dot FQDN, K/M and k principals, stub and FreeIPA BIND first hops), on new six- and four-VM topologies, with the metadata captured during the run. Results match; the control for the port form on 44e2d20 is added. Local checks from the same clean checkout: go test -race 57 ok, golangci-lint 0, playbook-lint, spec lint. The 287cf79 records are marked superseded; the dns and diagnose runbooks' §0.5 point at the new record. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
處理對
|
…ows, inputs - dns runbook §0.5 points at the b02b5cb record for the replica under another name; freeipa-dns-client runbook §0.5 adds the b02b5cb run; - the superseded records' banners name b02b5cb as current; - the b02b5cb record says which replica-spec rows failed in A and in B, that packages were recorded only for pr92-ipa and pr92-dns, that two inventory copies were not fingerprinted, and names main's spec files; - the diagnose runbook's §0.5 row no longer claims versions this run did not record. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
依獨立 review 對
CI 在 |
|
本次審查將 PR 轉回草稿,原因是發現以下阻擋合併的缺陷(審查 head: [P2] 新增的 replica forwarder task 會展開密碼中的 shell 特殊字元 位置:playbooks/apply/freeipa-server-replica-apply.yml:730。 具體新增的失敗路徑:對已啟用 DNS 的既有 replica 重跑 playbook,使用官方 single-host vm-target inventory(沒有 **實際重現:**原封抽出新增 task,在 localhost 用真實 **修正方向:**用 其餘 DNS/CLI focused race 測試與此 head 的 4 項 CI 均通過;通過的測試未涵蓋上述密碼情境。此缺陷修正並完成相關驗證前,先維持草稿,不合併。 |
…shell text
The replica forwarder task added in this PR ran
`printf %s "{{ ipa_admin_password }}" | kinit ...` in bash. Ansible puts
the password into the command text before the shell parses it, so `$`,
backticks and `$(...)` in it were expanded or run, and kinit got a
different password. On an existing DNS replica with the single-host
vm-target inventory this is the one kinit that runs, so a password the
other steps accept made the play fail (PR #92 review). The task now runs
`kinit admin@REALM` through command argv with the password on stdin; no_log
and the private ccache block are unchanged.
TestRegression_ReplicaForwarderKinitPassesPasswordUnchanged runs the real
task with ansible-playbook and a fake kinit. On c6bcfe9's playbook,
`demo$pilot92_missing_suffix` reaches kinit as `demo`, the backtick and
$(...) passwords run `touch`, and a password with quotes fails the play.
All six cases pass now.
Searched playbooks/** for the same pattern (AGENTS §5.10): 33 tasks on
main put a password-like variable into shell or free-form command text
(kinit, ipa passwd, htpasswd, ldapmodify -w, kadmin.local) across apply
playbooks, shared task files, check playbooks and test fixtures.
TestRegression_NoSecretInCommandText now fails for any new one; the 33
are a ratchet allowlist that fails when an entry stops matching, tracked
in issue #118.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
針對 c6bcfe9 審查的 [P2],修正已推到 修正
Regression test
同類寫法(§5.10)
本機驗證(
尚未完成:replica playbook 已變更, |
…k re-run on cdcd39b Candidate cdcd39b (tree 422ca9e) on new VMs, from a clean detached checkout, with metadata captured during the run. It repeats every check of the b02b5cb record: - topology tests A and B (L3-L6); - dns.md; - #91's diagnose cases; - round 2 (R2.1-R2.6) and round 1, with the 44e2d20 and main f9fb701 controls and the no-fix binary; - the replica spec on ipa4. All results match. It adds the review's P2 scenario: re-running the replica playbook on an existing DNS replica with the single-host vm-target inventory, after the FreeIPA admin password was changed to throwaway values containing $, backtick and $(...) commands, and quotes. - c6bcfe9 fails at the forwarder kinit. With the quote-free value, the touch commands in the password ran on the replica as root. - cdcd39b sets the forwarders, reruns with changed=0, and no command in the password ran. The record lists the git blob of all 646 execution files in 2026-10-16-cdcd39b-blobs.txt. b02b5cb's record is marked superseded, and the older banners and the dns, dns-architecture-diagnose and freeipa-dns-client runbook summaries now point to the new record. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
Candidate: 執行條件
重跑
P2 情境實跑(依審查描述:既有的 DNS replica、單機 vm-target inventory、沒有
本機檢查(同一個 checkout)
其他
|
…stdin PR #92 review of 3dbc0ef: cdcd39b fixed only the forwarder kinit. With an inventory that has a freeipa-server group, re-running the replica playbook still ran the self-register kinit (`printf %s "{{ ipa_enroll_password }}" | KRB5CCNAME=... kinit`) on the primary, and the primary's own dnsconfig-mod kinit, in the block this PR changed, had the same shell printf. Both now run kinit through command argv with the password on stdin; the self-register task sets KRB5CCNAME to its dedicated cache through environment. no_log is unchanged. TestRegression_FreeIPAKinitTasksPassPasswordUnchanged (was ...ReplicaForwarderKinit...) runs all three real tasks with ansible-playbook and a fake kinit, and checks the dedicated cache for the self-register task. On 3dbc0ef's playbooks the two newly covered tasks fail 4 of 6 cases each (kinit gets `demo`, the backtick and $(...) parts run, a quote fails the play). The lint (TestRegression_NoSecretInCommandText): - counts tasks per "<path>::<task name>". A copied task under an allowlisted name failed before only if the name differed; adding a second "Kinit admin" to gateway-scope-apply.yml now fails. - matches _pass, pwd, token, api_key and psk names too, and skips names ending in _file, _path or _dir. This finds audit_demo_user_pass in freeipa-audit-demo.yml (kadmin cpw) and freeipa-audit-sim.yml (kinit). The allowlist stays at 33 sites: these two in, the two fixed tasks out (issue #118). - says to use stdin: an argv element is not rewritten, but any local user can read it in /proc/<pid>/cmdline. The test comment no longer claims vault values are not templated: they are, so a {{ in a real password is rendered before any task sees it. AGENTS.md: §4.5 point 15, a §6 line and changelog v1.53. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
這次回覆 B1:同一支 playbook 還有兩處 shell kinit
現在這兩處都改成 行為測試改名為 Lint
本機驗證(
接下來 primary 與 replica 的 playbook 都改了,要凍結新的 candidate,再整輪重跑 VM。這次用 topology(多主機)inventory 加上含特殊字元的密碼,重跑 replica playbook,也就是 B1 描述的那條路徑。 文件類的意見會在新 record 一起處理:S1 的 dns.md 單機條件、N2 把 |
#106 now carries #103 merged with main, so the identity playbook's reconcile tasks sit inside main's private Kerberos credential cache block. This branch's playbook change is re-applied inside that block: the decommission HBAC and sudo reference queries list every rule with `--all --sizelimit=0` and fail on an error or a truncated result. Ignoring indentation, the change is the same as on candidate 0cad952. Version numbers: - docs/verification/freeipa-identity.md: main took v1.12, #103 v1.13 and #106 v1.14, so this branch's entry is v1.15. - AGENTS.md: main has v1.49 and v1.51, and its v1.51 row notes that #79 also adds v1.50. Open PRs use v1.50 (#79, #92), v1.52 (#80, #92) and v1.53 (#92), so §4.5 point 15's changelog entry is v1.54. Point 15 follows main's rewritten point 14. The tests needed no change: the option lint and the rule-reference tests reach the tasks inside the block. Putting `--host=` back fails TestRegression_PlaybookIPAOptionsExistInIPAHelp; dropping the sudo truncation check fails TestRegression_IdentityRuleReferenceQueriesStopOnErrors. Co-Authored-By: Claude Opus 5.5 <[email protected]>
b82a0c9 merges main afeacbd into dee1fe8: the replica self-register and primary dnsconfig-mod kinits now take the password on stdin too. It was run on new VMs from a clean detached checkout, with metadata captured during the run. It repeats every check of the cdcd39b record: - topology tests A and B (L3-L6) and dns.md; - #91's diagnose cases; - round 2 (R2.1-R2.6) and round 1, with the 44e2d20 and main f9fb701 controls and the no-fix binary; - the replica spec on ipa4. All results match. All three changed kinits ran in L4 and L6 with the vault password. Special-character admin passwords (one value with quotes, one without), each control against the candidate: - (a) single-host inventory, replica playbook: c6bcfe9 fails at the forwarder kinit, and the touch commands in the password run on the replica; - (b) topology inventory, where self-register runs delegated to the primary: 3dbc0ef fails at the self-register kinit, and the commands run on the primary; - (c) freeipa-server-apply.yml --tags freeipa-verify, whose only password-reading command task is the dnsconfig-mod kinit: 3dbc0ef fails there, and the commands run on the primary. The candidate succeeds and reruns with changed=0 in every case, with no markers. The password was restored afterwards; the log scan found none. Review fixes: - S1: the dns runbook result row states the inventory condition for each case and that #118's shell kinits still break. - N2: the blob list adds contracts/ and ansible_callback/ and says which directories count. - N3: the record's tenses match what has happened. - N6: the behavioural test's evidence is the local -v run (18/18 on ansible-core 2.19.2). CI's runner image lists Ansible 2.21.5, so the test may run there rather than skip. cdcd39b's record is marked superseded. Its banner notes that its diagnose server used an empty --dir, so contracts were not loaded. The older banners point to the new record. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
第 6 輪實跑完成,evidence 已推到 Candidate
完整重跑
特殊字元密碼實跑(兩組一次性密碼:一組含引號,一組不含)
N6 需要更正 其他文件修正
本機檢查(同一個 checkout)
|
Follow-up to #73 (merged as
f9fb701). Six rounds; the first two reviews: the second review of #73's head5e48d67(round 1, candidate01a64f4), and a review of this PR's head44e2d20(round 2, candidate09ded7e, then5750f2aafter my own adversarial review).freeipa-server-apply.ymlnow sets two lists toipa_dns_forwarders/freeipa_dns_forwarderson every run:ipa dnsconfig-mod);ipa dnsserver-mod <fqdn>). FreeIPA stores the install-time--forwarderthere, and named prefers it over the global list.An explicitly empty value (
"",[], null) clears both on an existing realm. FreeIPA's named then answers only its own zones. Before, an empty value skipped this step, and a changed list never reached the server's install-time forwarders.freeipa-server-replica-apply.ymllikewise sets a replica's own forwarders. Unset is the default; it clears them, and the replica uses the global list.Leaving the variable unset on the primary still means
8.8.8.8. Documented infreeipa-server.mdv1.9,freeipa-server-replica.mdv1.2 andgroup_vars/freeipa.example.yml.Current evidence (candidate
b82a0c9)b82a0c9(treeb733b150) is this PR with the round 6 fix (dee1fe8), merged withmainafeacbd(724cc45, a webhook fix ininternal/outbound, plus an evidence record). CI passed on it (run 38880469725). The head adds only the evidence record and doc summaries.Record:
docs/evidence/freeipa-replica-dns-provider/2026-10-16-b82a0c9.md. It repeats every check of thecdcd39brecord on this candidate, with the same44e2d20andmainf9fb701controls and the no-fix diagnose binary:dns.md16 pass / 4 n/a. All three changedkinittasks ran in L4 and L6 with the vault password.K/M/kprincipals, and the stub and FreeIPA BIND as the first hop. Each diagnose server now uses its checkout as--dir, so the supported layer reports the component contracts. In thecdcd39brun it was an empty directory.site.yml, and promotion. Every result matches the earlier records.$, backtick and$(...)commands, one with quotes and one without. Each control fails, and with the quote-free value thetouchcommands in the password run as root.b82a0c9succeeds, the rerun giveschanged=0, and nothing in the password runs.freeipa-servergroup; the self-register block is skipped), replica playbook on an existing DNS replica.c6bcfe9fails at the forwarderkinit, and the commands run on the replica.3dbc0effails at the self-registerkinit, and the commands run on the primary.freeipa-server-apply.yml --tags freeipa-verify. Of the 7 command tasks there and inplaybooks/apply/tasks/that read an admin or Directory Manager password, this tag selects only the dnsconfig-modkinit.3dbc0effails at thatkinit, and the commands run on the primary.kinits andldapmodify -wstill onmain(bug(playbooks): 33 個 task 把密碼插進 shell/command 字串,密碼中的 $、反引號、引號會被改寫或執行 #118) would still break with such a password, so the full chain was not run with one. A scan of every log and diagnose result found no password.TestRegression_FreeIPAKinitTasksPassPasswordUnchangedpassed all 18 subtests locally, with ansible-core 2.19.2 and-v. CI's Go test job installs no Ansible, but its runner image (ubuntu-24.0420261012.334) lists Ansible 2.21.5, so the test may run in CI rather than skip;go testwithout-vdoes not show which. Only the local run counts as evidence.docs/actual-run-evidence.md§3 fields were captured during the run. The record lists the git blob of all 688 execution files, now includingcontracts/andansible_callback/, and says which directories count.go test -race57 ok, golangci-lint 2.12.1 0 issues, playbook-lint, spec lint 0 findings.The earlier records (
cdcd39b,b02b5cb,287cf79×3,5750f2a,09ded7e,01a64f4) are marked superseded. The round sections below describe the changes; the record above has their verification.Round 6: the self-register and dnsconfig-mod
kinits (review of3dbc0ef)dee1fe8). Two more tasks gave the admin password tokinitin a shell command line: the replica self-registerkinit, which runs whenever the inventory has afreeipa-servergroup, and the primary's dnsconfig-modkinit. Both now runkinitthroughargvwith the password onstdin. The self-registerKRB5CCNAMEcomes from the task'senvironment.TestRegression_FreeIPAKinitTasksPassPasswordUnchangedruns all three real tasks withansible-playbookand a fakekinit, and checks that self-register uses its dedicated cache.<file>::<task name>. The name pattern adds_pass,pwd,token,api_keyandpsk, and excludes_file,_pathand_dir. The allowlist stays at 33 entries; bug(playbooks): 33 個 task 把密碼插進 shell/command 字串,密碼中的 $、反引號、引號會被改寫或執行 #118 is updated.mainafeacbdtouches no file on this PR's DNS or diagnose path, but it changes thepilotbinary.Round 5: the replica forwarder
kinit(review ofc6bcfe9; candidatecdcd39b, superseded by round 6)kinitasprintf %s "{{ ipa_admin_password }}" | kinit …in bash. Ansible puts the value into the command text before the shell parses it, so$, backticks and$(...)were expanded or run. It now runsargv: [kinit, "admin@{{ ipa_realm }}"]with the password onstdin;no_logand the private credential cache are unchanged.TestRegression_ReplicaForwarderKinitPassesPasswordUnchangedruns the real task withansible-playbookand a fakekinit. Onc6bcfe9, four of its six passwords fail:kinitgetsdemo,aborxy, thetouchcommands run, or a password with quotes fails the play.mainput a password-like variable into shell or free-form command text. The newTestRegression_NoSecretInCommandTextfails for any new one. The 33 are a ratchet allowlist that also fails when an entry stops matching; migrating them is bug(playbooks): 33 個 task 把密碼插進 shell/command 字串,密碼中的 $、反引號、引號會被改寫或執行 #118.mainf98327b(fix(access): parallelize gateway queries and clarify API timeouts #112, ci: raise the Go test timeout to 30m #116) touches no file on this PR's DNS or diagnose path, but it changes thepilotbinary, so every check was re-run.Round 4: merged with
mainagain; left-out replicas in the paths (candidateb02b5cb, superseded by round 5)maincc38f58changed files on this PR's tested path: the private-cache task files the FreeIPA playbooks include, andinternal/spec/generator.go. It had no conflicts.BuildDNSArchPathsprinted a left-out replica as "FreeIPA DNS replica … authoritative for ". It now says that replica is left out of the DNS paths.TestBuildDNSArchPaths_LeftOutReplicaIsNotAuthoritativefails without the fix.Round 2
A replica that answers is kept.
tasks/freeipa-dns-providers-live.ymlkeeps a replica whose own+norecNS answer is NOERROR withaa, even when its inventory name is not in FreeIPA's NS records. Before, a replica installed under another name was dropped silently.+tries=2.pilot_diagnose_dns_architecturefollows the same rule.The resolver check blocks only what must be blocked. The new
tasks/freeipa-dns-client-nameserver-check.ymlasks each nameserver for A and AAAA. It fails only when:A server that is down now gets a WARNING with a remedy for its role, and stays in the list: a listed FreeIPA server, a dns tier host, or an explicit entry. Only FreeIPA servers are asked with
+norec. The tier's G5 still fails closed when a listed FreeIPA server does not answer; that is documented in the dns runbook.Address check. Python
ipaddresschecks forwarders on the controller, and Gonet/netipin the diagnose model.TestNormalizeForwardersMatchesPlaybookGatenow covers 15 more inputs."<address> port <n>"is accepted (checked live withipa dnsconfig-modanddnsserver-modon 4.13.4).pre_tasksgate stops a port form before any change when the run would install.Adversarial-review fixes (
5750f2a).pilot_diagnose_dns_architectureand its runbook no longer call per-server forwarders unmanaged. A mismatch now says to re-apply.::ffff:0.0.0.0is refused).Visibility.
dns-apply.ymlprintsDNS — summary: FreeIPA servers the stubs use: the effective servers, the ones left out, and who answered.Round 1 (
01a64f4, unchanged)Verification (round 2)
History: these results are for
5750f2a, whose record is superseded. Every check was repeated on the current candidate; see Current evidence.Records:
docs/evidence/freeipa-replica-dns-provider/2026-10-15-09ded7e.mdhas the control comparisons against44e2d20.…/2026-10-15-5750f2a.mdhas the final candidate, re-run on a new freshpr92-topology.5750f2a; also green on09ded7e):go test -race -count=1 -timeout 60m ./...: 57 packages ok.make playbook-lint: pass.TestRegression_ResolverNameserverCheckhas 10 cases from real dig captures. The old gate fails the down-replica, IPv6-only and explicit-timeout cases.TestDNSArchUnregisteredReplicas_KeepAReplicaThatAnswersItselffails on44e2d20.44e2d20.::ffff:0.0.0.0fails on09ded7e.pr92-topology, run twice:09ded7e, then5750f2aon new VMs (6 VMs: FreeIPA 4.13.4 primary and two replicas, one more replica listed but never promoted, an unbound tier, a consumer):failed=0; L4failed=0; L6changed=0.freeipa-server20/0,freeipa-server-replica16/0,freeipa-dns-client7/0,dns16 pass / 4 n/a.44e2d2009ded7e/5750f2a.4 .7 .8check named on pr92-r2freeipa_dns_forwarders: []on the existing realm8.8.8.8stays; diagnosemismatchmatch"1.1.1.1 port 53 9.9.9.9"on the existing realm"8.8.8.8,1.1.1.1",port 0,port 65536,0.0.0.0,224.0.0.1,:::,fe80::1%eth0,{})Diagnose on the final state:
complete, 12/12match, finding only for the unpromoted replica.Round 3: merged with
main(#91), candidate287cf79(superseded by round 4)#91 merged into
mainas306c444first, so this PR resolved the conflict, as agreed.287cf79(tree1cbaac4f) isfde3ce4merged with306c444. Record:docs/evidence/freeipa-replica-dns-provider/2026-10-15-287cf79.md.… || !p.DNSProvider || p.LeftOut || …).TestBuildDNSArchDifferences_LeftOutReplicaIsNotComparedWithTheReferencefails without that change.TestBuildDNSArchStatus_UnknownConfigAndComparisonsAreNamednow expects this PR's subject,(ipa dnsserver-mod).freeipa_dns_not_authoritative_from_tier.5750f2aaremain's blobs, unchanged. No playbook, task file, fixture, contract or spec changed.287cf79):go test -race -count=1 -timeout 60m ./...: 57 packages ok.make playbook-lint: pass.pr92-topology, run from that checkout, with the §3 metadata captured during the run (run IDs, UTC window, base image sha256, package versions, input digests):failed=0; L4failed=0; L6changed=0.dns.md16 pass / 4 n/a.complete, 14/14match, with onlyfreeipa_replica_not_dns_serverfor the unpromoted replica. A control binary, the same tree without the left-out skip, gavepartialwithcomparison_unknownfor that replica.K/M/k) were not re-run on287cf79.🤖 Generated with Claude Code