mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
fix(svc-net-tor): tie the dnsmasq listener to ownership, not the deploy mode
Outside compose TOR_CONTAINER_DNS_HOST was empty, so the drop-in rendered no
listen-address and lookup('container_dns') handed containers the clearnet
resolver alone. A swarm stack host ran the egress router without a listener its
containers could reach, and .onion stayed unresolvable for every one of them
while compose resolved it fine. Ownership already governs the listener, the
upstream policy and the resolv.conf pin; the deploy mode only ever stood in for
it. With the mode clause gone both modes render the drop-in compose has been
running in production, and compose keeps its exact behaviour: where matched == 0
the old and the new expression are the same boolean.
In CI run 31927586060 the swarm node dnsmasq logged "ignoring query from
non-local network 172.18.0.16" seconds after a veth appeared and vanished on
docker0, and every container lookup timed out from then on. The health service
retried for 77 minutes against a resolver that no longer answered.
Measured on dnsmasq 2.93 before landing this: a listen-address disables
--local-service even when that flag comes from the init script; without a bind
directive dnsmasq takes the 0.0.0.0 and [::] wildcard pair, which would collide
with a systemd-resolved stub and occupy port 53 on every address; bind-dynamic
and bind-interfaces both hold their per-address listeners across 2000 veth
cycles; and the stock dnsmasq.conf on arch, fedora and centos carries no
bind-interfaces, so the drop-in's bind-dynamic cannot abort startup there.
Two expectations in tests/unit/plugins/lookup/test_container_dns.py still encode
the compose-only asymmetry and fail with this change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
dcb921c260
commit
b09f2cbd82
3 files changed
+5
-14
No files matched your search
@@ -5,7 +5,6 @@ from typing import Any
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
|
||||
_TOR_ROLE = "svc-net-tor"
|
||||
_BRIDGE_MODE = "compose"
|
||||
|
||||
|
||||
def resolve_container_dns(variables: dict[str, Any]) -> list[str]:
|
||||
@@ -17,8 +16,8 @@ def resolve_container_dns(variables: dict[str, Any]) -> list[str]:
|
||||
bridge address because docker discards loopback resolvers, and it is
|
||||
absent before docker itself is installed, hence the guard.
|
||||
|
||||
Only compose mode gets the bridge: TOR_CONTAINER_DNS_HOST creates the
|
||||
matching dnsmasq listener there and nowhere else.
|
||||
Both deploy modes get the bridge: a Tor node runs the same dnsmasq
|
||||
listener in compose and in swarm, so both resolve onions alike.
|
||||
|
||||
The clearnet resolver stays as a second entry so that losing dnsmasq costs
|
||||
onion names rather than all name resolution.
|
||||
@@ -26,19 +25,12 @@ def resolve_container_dns(variables: dict[str, Any]) -> list[str]:
|
||||
facts = variables.get("ansible_facts") or {}
|
||||
bridge = ((facts.get("docker0") or {}).get("ipv4") or {}).get("address") or ""
|
||||
on_tor_node = _TOR_ROLE in (variables.get("group_names") or [])
|
||||
bridge_reachable = (
|
||||
str(variables.get("DEPLOYMENT_MODE") or "").strip() == _BRIDGE_MODE
|
||||
)
|
||||
|
||||
clearnet = ((variables.get("networks") or {}).get("internet") or {}).get(
|
||||
"dns"
|
||||
) or ""
|
||||
|
||||
return [
|
||||
str(r)
|
||||
for r in (bridge if (on_tor_node and bridge_reachable) else "", clearnet)
|
||||
if r
|
||||
]
|
||||
return [str(r) for r in (bridge if on_tor_node else "", clearnet) if r]
|
||||
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
fail_msg: >-
|
||||
networks.internet.dns must be set on a Tor node: dnsmasq runs with
|
||||
no-resolv and would answer .onion only.
|
||||
when: DEPLOYMENT_MODE == 'compose'
|
||||
- name: "📦 Egress router | Install dnsmasq (host split-DNS forwarder)"
|
||||
become: true
|
||||
package_install:
|
||||
@@ -35,7 +34,7 @@
|
||||
register: _tor_dnsmasq_foreign
|
||||
- name: "🔧 Egress router | Decide whether this dnsmasq is ours to reshape"
|
||||
ansible.builtin.set_fact:
|
||||
TOR_DNSMASQ_OWNS_LISTENER: "{{ _tor_dnsmasq_foreign.matched == 0 and DEPLOYMENT_MODE == 'compose' }}"
|
||||
TOR_DNSMASQ_OWNS_LISTENER: "{{ _tor_dnsmasq_foreign.matched == 0 }}"
|
||||
|
||||
- name: "📝 Egress router | Render dnsmasq .onion split-DNS drop-in"
|
||||
ansible.builtin.template: # nocheck: stack-host-template (per-host dnsmasq system config in /etc/dnsmasq.d)
|
||||
|
||||
@@ -13,7 +13,7 @@ TOR_FLAVOR: "{{ lookup('config', application_id, 'services.tor.flavor', 'public'
|
||||
TOR_TRANS_PORT: "{{ lookup('config', application_id, 'services.tor.ports.local.trans') }}"
|
||||
TOR_DNS_PORT: "{{ lookup('config', application_id, 'services.tor.ports.local.dns') }}"
|
||||
TOR_DNSMASQ_CONF: "/etc/dnsmasq.d/tor-onion.conf"
|
||||
TOR_CONTAINER_DNS_HOST: "{{ ansible_facts['docker0']['ipv4']['address'] | default('', true) if DEPLOYMENT_MODE == 'compose' else '' }}"
|
||||
TOR_CONTAINER_DNS_HOST: "{{ ansible_facts['docker0']['ipv4']['address'] | default('', true) }}"
|
||||
TOR_HS_BACKEND_HOST: "{{ ansible_facts['default_ipv4']['address'] if DEPLOYMENT_MODE == 'swarm' else (TOR_CONTAINER_DNS_HOST | ternary(TOR_CONTAINER_DNS_HOST, '127.0.0.1'))
|
||||
}}"
|
||||
|
||||
|
||||
Reference in new issue
Block a user