mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
feat(svc-net-tor): add a chutney flavor for private-net test deployments
The test variant (meta/variants.yml, variant 0) switches the tor service to the chutney flavor: the image ships chutney and the entrypoint boots a private Tor network inside the container (all nodes on 127.0.0.1, so the design is identical under compose host-networking and swarm). The entrypoint bootstraps from /opt/chutney (chutney creates its net/ state CWD-relative), extracts the DirAuthority/ TestingTorNetwork lines into /run/tor/chutney-net.conf and fails fast when that file ends up empty, since the node would otherwise silently join the public network. torrc %includes the conf only for the chutney flavor. Descriptor publish and rendezvous now run at localhost speed: per-app onion e2e suites dropped from minutes to seconds in the compose validation. The test variant also raises the footprint to 2g/1.0cpu/512pids (measured: at 1g/0.25 the ~9 extra tor nodes pin the caps and desc publishing stalls); meta/services.yml stays static on the public flavor and the old production footprint. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4afc9b7702
commit
5399b48482
10 files changed
+117
-4
No files matched your search
@@ -2,10 +2,23 @@ ARG TOR_BASE_IMAGE
|
||||
ARG TOR_BASE_VERSION
|
||||
FROM ${TOR_BASE_IMAGE}:${TOR_BASE_VERSION}
|
||||
|
||||
ARG TOR_FLAVOR
|
||||
ENV TOR_FLAVOR=${TOR_FLAVOR}
|
||||
|
||||
RUN rm -rf /var/lib/apt/lists/* \
|
||||
&& apt-get -o Acquire::Retries=3 update \
|
||||
&& apt-get -o Acquire::Retries=3 install -y --no-install-recommends tor \
|
||||
&& if [ "${TOR_FLAVOR}" = "chutney" ]; then \
|
||||
apt-get -o Acquire::Retries=3 install -y --no-install-recommends \
|
||||
git ca-certificates python3 python3-cryptography python3-networkx \
|
||||
python3-paramiko python3-rpyc python3-tomli-w python3-typeguard \
|
||||
python3-typing-extensions \
|
||||
&& git clone --depth=1 https://gitlab.torproject.org/tpo/core/chutney /opt/chutney ; \
|
||||
fi \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENTRYPOINT ["tor"]
|
||||
COPY entrypoint.sh /usr/local/bin/tor-entrypoint.sh
|
||||
RUN chmod 0755 /usr/local/bin/tor-entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/tor-entrypoint.sh"]
|
||||
CMD ["-f", "/etc/tor/torrc"]
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/bin/sh
|
||||
# Tor container entrypoint.
|
||||
#
|
||||
# public flavor: exec tor directly against the bind-mounted torrc.
|
||||
# chutney flavor: first bring up a private, offline Tor test network with
|
||||
# chutney inside this container (all nodes on 127.0.0.1), extract its
|
||||
# authority/testing lines into the file the rendered torrc %includes, then
|
||||
# exec the node tor joined to that private net. Descriptor publication and
|
||||
# rendezvous then run at localhost speed instead of over the public network.
|
||||
set -eu
|
||||
|
||||
if [ "${TOR_FLAVOR}" = "chutney" ]; then
|
||||
net="hs-v3-min"
|
||||
conf=/run/tor/chutney-net.conf
|
||||
mkdir -p /run/tor
|
||||
|
||||
echo "[tor-entrypoint] bringing up chutney private network '${net}'"
|
||||
# Exception: chutney creates its net/ state dir relative to the CWD, so
|
||||
# every call must run from /opt/chutney or the torrc grep below misses.
|
||||
cd /opt/chutney
|
||||
CHUTNEY_LISTEN_ADDRESS=127.0.0.1 \
|
||||
./chutney init --net "${net}"
|
||||
./chutney configure
|
||||
./chutney start
|
||||
./chutney wait_for_bootstrap
|
||||
|
||||
grep -hE '^(TestingTorNetwork|DirAuthority|DirServer|AlternateDirAuthority) ' \
|
||||
net/nodes/*/torrc | sort -u >"${conf}"
|
||||
if [ ! -s "${conf}" ]; then
|
||||
echo "[tor-entrypoint] ERROR: ${conf} is empty — the node would silently join the public Tor network" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[tor-entrypoint] wrote ${conf} ($(wc -l <"${conf}") line(s))"
|
||||
cd /
|
||||
fi
|
||||
|
||||
exec tor "$@"
|
||||
@@ -25,6 +25,7 @@ tor:
|
||||
name: tor
|
||||
node: ""
|
||||
use_entry_guards: true
|
||||
flavor: public
|
||||
ports:
|
||||
local:
|
||||
socks: 9050
|
||||
|
||||
@@ -3,7 +3,14 @@
|
||||
# See docs/contributing/artefact/files/role/variants.md for the file format.
|
||||
|
||||
# Test deployments run without guard pinning so a single degraded entry guard
|
||||
# cannot stall the whole node; production keeps the Tor default (true).
|
||||
# cannot stall the whole node; production keeps the Tor default (true). They
|
||||
# also join a private chutney Tor net (offline, localhost-speed onion publish
|
||||
# and rendezvous) whose ~9 extra in-container tor nodes need a larger resource
|
||||
# footprint than the public flavor.
|
||||
- services:
|
||||
tor:
|
||||
use_entry_guards: false # nocheck: variants-static-override
|
||||
flavor: chutney
|
||||
cpus: "1.0"
|
||||
mem_limit: 2g
|
||||
pids_limit: 512
|
||||
@@ -1,6 +1,16 @@
|
||||
---
|
||||
- include_tasks: utils/once/flag.yml
|
||||
|
||||
- name: "✅ Assert the Tor network flavor is public or chutney"
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- TOR_FLAVOR in ['public', 'chutney']
|
||||
fail_msg: "services.tor.flavor must be 'public' or 'chutney' (got '{{ TOR_FLAVOR }}')"
|
||||
quiet: true
|
||||
|
||||
- name: "📤 Dispatch Tor network flavor '{{ TOR_FLAVOR }}'"
|
||||
ansible.builtin.include_tasks: "flavor/{{ TOR_FLAVOR }}/main.yml"
|
||||
|
||||
- name: "🧅 Ensure Tor config directory exists"
|
||||
ansible.builtin.file:
|
||||
path: "{{ TOR_CONFIG_DIR }}"
|
||||
@@ -47,6 +57,23 @@
|
||||
become: true
|
||||
register: _tor_torrc
|
||||
|
||||
- name: "📁 Ensure the Tor compose instance dir exists (build context)"
|
||||
ansible.builtin.file:
|
||||
path: "{{ TOR_INSTANCE_DIR }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
become: true
|
||||
|
||||
- name: "🚀 Stage the Tor container entrypoint into the build context"
|
||||
stack_host_copy:
|
||||
src: "{{ role_path }}/files/entrypoint.sh"
|
||||
dest: "{{ TOR_ENTRYPOINT_HOST }}"
|
||||
mode: "0755"
|
||||
become: true
|
||||
register: _tor_entrypoint
|
||||
|
||||
- name: "🧩 For '{{ application_id }}': Load sys-svc-compose"
|
||||
include_role:
|
||||
name: sys-svc-compose
|
||||
@@ -63,7 +90,7 @@
|
||||
chdir: "{{ lookup('container', application_id, 'directories.instance') }}"
|
||||
executable: /bin/bash
|
||||
become: true
|
||||
when: DEPLOYMENT_MODE == 'compose' and ((_tor_hs_keys is changed) or (_tor_torrc is changed))
|
||||
when: DEPLOYMENT_MODE == 'compose' and ((_tor_hs_keys is changed) or (_tor_torrc is changed) or (_tor_entrypoint is changed))
|
||||
|
||||
- name: "🔄 Force Tor service redeploy to apply hidden-service key / torrc changes"
|
||||
ansible.builtin.command: >-
|
||||
@@ -75,7 +102,7 @@
|
||||
when:
|
||||
- DEPLOYMENT_MODE == 'swarm'
|
||||
- IS_STACK_HOST | bool
|
||||
- (_tor_hs_keys is changed) or (_tor_torrc is changed)
|
||||
- (_tor_hs_keys is changed) or (_tor_torrc is changed) or (_tor_entrypoint is changed)
|
||||
|
||||
- name: "🧅 Wire the host-level transparent .onion egress router"
|
||||
include_tasks:
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: "🧪 Tor flavor 'chutney' — join a private in-container Tor test net"
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
svc-net-tor joins a private chutney Tor network bootstrapped inside the
|
||||
tor container (all nodes on 127.0.0.1). Descriptor publication and
|
||||
rendezvous run at localhost speed; the onion is reachable only from
|
||||
hosts sharing this private net (the node's own SOCKS), which is exactly
|
||||
the test surface. The heavy lifting is the container entrypoint and the
|
||||
torrc %include of /run/tor/chutney-net.conf.
|
||||
changed_when: false
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
- name: "🌍 Tor flavor 'public' — join the public Tor network"
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
svc-net-tor runs against the public Tor network; onion descriptor
|
||||
publication and rendezvous take real-network latency.
|
||||
changed_when: false
|
||||
@@ -6,6 +6,7 @@
|
||||
args:
|
||||
TOR_BASE_IMAGE: "{{ TOR_IMAGE }}"
|
||||
TOR_BASE_VERSION: "{{ TOR_VERSION }}"
|
||||
TOR_FLAVOR: "{{ TOR_FLAVOR }}"
|
||||
{{ lookup('compose_only', 'container_name', TOR_CONTAINER) }}
|
||||
{{ lookup('container_image', application_id, 'tor') }}
|
||||
{% if DEPLOYMENT_MODE == 'swarm' %}
|
||||
@@ -24,6 +25,7 @@
|
||||
interval: 60s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: {{ '150s' if TOR_FLAVOR == 'chutney' else '10s' }}
|
||||
|
||||
{{ lookup('compose_volumes', application_id) }}
|
||||
{{ lookup('compose_networks') }}
|
||||
@@ -16,6 +16,10 @@ CookieAuthentication 1
|
||||
UseEntryGuards 0
|
||||
{% endif %}
|
||||
|
||||
{% if TOR_FLAVOR == 'chutney' %}
|
||||
%include /run/tor/chutney-net.conf
|
||||
{% endif %}
|
||||
|
||||
{% if TOR_EGRESS_ROUTER_ENABLED | bool %}
|
||||
AutomapHostsOnResolve 1
|
||||
AutomapHostsSuffixes .onion
|
||||
|
||||
@@ -9,6 +9,7 @@ TOR_CONTAINER: "{{ lookup('config', application_id, 'services.tor.name') }}"
|
||||
TOR_SOCKS_PORT: "{{ lookup('config', application_id, 'services.tor.ports.local.socks') }}"
|
||||
TOR_NODE_ONION: "{{ lookup('config', application_id, 'services.tor.node', '') }}"
|
||||
TOR_USE_ENTRY_GUARDS: "{{ lookup('config', application_id, 'services.tor.use_entry_guards') | bool }}"
|
||||
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"
|
||||
@@ -21,3 +22,6 @@ TOR_TORRC_HOST: "{{ [ TOR_CONFIG_DIR, 'torrc' ] | path_join }}"
|
||||
TOR_HS_DIR: "{{ [ TOR_DATA_DIR, 'infinito', 'node' ] | path_join }}"
|
||||
|
||||
TOR_HS_SRC_DIR: "{{ lookup('path_absolute', '.onion-identity', 'hs') }}"
|
||||
|
||||
TOR_INSTANCE_DIR: "{{ lookup('container', application_id, 'directories.instance') }}"
|
||||
TOR_ENTRYPOINT_HOST: "{{ [ TOR_INSTANCE_DIR, 'entrypoint.sh' ] | path_join }}"
|
||||
Reference in new issue
Block a user