mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 19:01:54 +00:00
Sending to a .onion needs two things of the node: a resolver that answers the suffix, and a route from the container to Tor's transparent port. Neither existed. Compose pointed Mailu at unbound, which knows nothing about .onion, and in swarm Tor's DNSPort and TransPort listen inside the container where the host's dnsmasq and nat rules cannot reach them, so the forwarding rules that were already there pointed at a closed port on every swarm node. Compose resolves through unbound, which now forwards the onion zone to Tor's DNSPort on the bridge it already owns. The forward alone would have answered SERVFAIL: the signed root proves that onion does not exist, so with auto-trust-anchor-file set every answer below it validates as bogus, and Tor's DNSPort can supply no DS. The zone is therefore declared insecure. Swarm resolves through the node dnsmasq instead, which every container already uses because container_dns puts the bridge resolver into daemon.json wherever svc-net-tor is deployed, and which now reaches Tor because its ports are published host-mode next to the SOCKS port that was published there already. The transport rule changed target rather than mechanism. REDIRECT rewrites the destination to the primary address of the interface the packet arrived on, which is a different project bridge for every role, so Tor would have had to listen on all of them. A DNAT naming the bridge keeps one listener, and in swarm the same address reaches the published port through the proxy docker runs there. That leaves the exposure. SocksPort has SocksPolicy; DNSPort and TransPort have no equivalent, and a swarm-published port carries no host address to bind to, so in swarm the packet filter is their only boundary. Compose needs none, because there Tor binds loopback and the bridge and nothing else. The swarm guard admits this deployment's own address space and drops the rest, and it is installed as a unit ordered after docker rather than as ansible-applied rules: nothing here persists netfilter state, so docker recreating its chains at boot would have left Tor back on the wildcard with the guard gone. It rebuilds its chain instead of appending to it, because the appending version could only ever be loosened. It hooks INPUT and DOCKER-USER both, since host-local traffic reaches a published port through docker's proxy and external traffic through FORWARD. The deploy now proves all of it back and fails when a rule is missing, the router no longer runs on swarm nodes that carry no Tor, and the CLI test probes the forwarded ports over SOCKS instead of assuming a HiddenServicePort line means a reachable service. Measured so far: Tor accepts the non-loopback binds and reports circuit-established, and the chain, its order and the DOCKER-USER hooks read back correctly from a deployed host. The swarm path is not yet exercised. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
37 lines
1.6 KiB
Django/Jinja
37 lines
1.6 KiB
Django/Jinja
{% include 'roles/sys-svc-compose/templates/base.yml.j2' %}
|
|
{% set service_name = 'tor' %}
|
|
{{ service_name }}:
|
|
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
|
{{ lookup('template', 'roles/sys-svc-container/templates/build.yml.j2') | indent(4) }}
|
|
args:
|
|
TOR_BASE_IMAGE: "{{ TOR_IMAGE }}"
|
|
TOR_BASE_VERSION: "{{ TOR_VERSION }}"
|
|
TOR_FLAVOR: "{{ TOR_FLAVOR }}"
|
|
TOR_ENTRYPOINT_PATH: "{{ TOR_ENTRYPOINT_PATH }}"
|
|
TOR_HEALTHCHECK_PATH: "{{ TOR_HEALTHCHECK_PATH }}"
|
|
{{ lookup('compose_only', 'container_name', TOR_CONTAINER) }}
|
|
{{ lookup('container_image', application_id, 'tor') }}
|
|
{% if DEPLOYMENT_MODE == 'swarm' %}
|
|
{{ lookup('container_ports',
|
|
{'service': 'tor', 'protocol': 'socks', 'mode': 'host',
|
|
'container': 'same'},
|
|
{'service': 'tor', 'protocol': 'dns', 'mode': 'host',
|
|
'container': 'same', 'transport': ['udp', 'tcp']},
|
|
{'service': 'tor', 'protocol': 'trans', 'mode': 'host',
|
|
'container': 'same', 'transport': 'tcp'}) | indent(4) }}
|
|
{{ lookup('container_networks') | indent(4) }}
|
|
{% else %}
|
|
network_mode: "host"
|
|
{% endif %}
|
|
{{ lookup('container_volumes', service_name) | indent(4) }}
|
|
healthcheck:
|
|
test: ["CMD", "{{ TOR_HEALTHCHECK_PATH }}", "{{ lookup('volume', 'svc-net-tor', 'tor_data').target }}"]
|
|
interval: 2m
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 10m
|
|
start_interval: 5s
|
|
|
|
{{ lookup('compose_volumes') }}
|
|
{{ lookup('compose_networks') }}
|