diff --git a/playbooks/amazon.yml b/playbooks/amazon.yml index 72eb6df3d..d4ff66dea 100644 --- a/playbooks/amazon.yml +++ b/playbooks/amazon.yml @@ -1,4 +1,8 @@ --- +# Randomize Tor's ORPort and obfs4 port if Tor is enabled. +# We do this early so ec2-security-group knows Tor's port +- import_playbook: tor-setup.yml + - name: Provision the EC2 Server # ============================== hosts: localhost diff --git a/playbooks/azure.yml b/playbooks/azure.yml index e34115d4b..6eabbd36d 100644 --- a/playbooks/azure.yml +++ b/playbooks/azure.yml @@ -1,4 +1,8 @@ --- +# Randomize Tor's ORPort and obfs4 port if Tor is enabled. +# We do this early so azure-security-group knows Tor's port +- import_playbook: tor-setup.yml + - name: Provision the Azure Server (Resource Manager mode) # ======================================================== hosts: localhost diff --git a/playbooks/digitalocean.yml b/playbooks/digitalocean.yml index 3ba5fd9e2..ddcda8780 100644 --- a/playbooks/digitalocean.yml +++ b/playbooks/digitalocean.yml @@ -1,4 +1,7 @@ --- +# Randomize Tor's ORPort and obfs4 port if Tor is enabled. +- import_playbook: tor-setup.yml + - name: Provision the DigitalOcean Server # ======================================= hosts: localhost diff --git a/playbooks/existing-server.yml b/playbooks/existing-server.yml index 2430f4bc9..0188ef955 100644 --- a/playbooks/existing-server.yml +++ b/playbooks/existing-server.yml @@ -3,6 +3,9 @@ # role to create a new server and instead applies Streisand to an existing # remote server. +# Randomize Tor's ORPort and obfs4 port if Tor is enabled. +- import_playbook: tor-setup.yml + - name: Register the genesis role in use hosts: localhost gather_facts: yes diff --git a/playbooks/google.yml b/playbooks/google.yml index 8996f583c..6524ece31 100644 --- a/playbooks/google.yml +++ b/playbooks/google.yml @@ -1,4 +1,8 @@ --- +# Randomize Tor's ORPort and obfs4 port if Tor is enabled. +# We do this early so gce-network knows Tor's port +- import_playbook: tor-setup.yml + - name: Provision the GCE Server # ======================================= hosts: localhost diff --git a/playbooks/linode.yml b/playbooks/linode.yml index f65af00a8..16218fd4e 100644 --- a/playbooks/linode.yml +++ b/playbooks/linode.yml @@ -1,4 +1,7 @@ --- +# Randomize Tor's ORPort and obfs4 port if Tor is enabled. +- import_playbook: tor-setup.yml + - name: Provision the Linode Server # ================================= hosts: localhost diff --git a/playbooks/localhost.yml b/playbooks/localhost.yml index 010dab608..205a16fcd 100644 --- a/playbooks/localhost.yml +++ b/playbooks/localhost.yml @@ -2,6 +2,10 @@ # localhost.yml is an advanced provisioning option that doesn't use a genesis # role to create a new server and instead applies Streisand to the localhost. +# Randomize Tor's ORPort and obfs4 port if Tor is enabled. +# We do this early so gce-network knows Tor's port +- import_playbook: tor-setup.yml + # Ensure Python is installed on the system - import_playbook: python.yml diff --git a/playbooks/rackspace.yml b/playbooks/rackspace.yml index 8e6d05a68..890769a8d 100644 --- a/playbooks/rackspace.yml +++ b/playbooks/rackspace.yml @@ -1,4 +1,7 @@ --- +# Randomize Tor's ORPort and obfs4 port if Tor is enabled. +- import_playbook: tor-setup.yml + - name: Provision the Rackspace Server # ==================================== hosts: localhost diff --git a/playbooks/roles/tor-bridge/defaults/main.yml b/playbooks/roles/tor-bridge/defaults/main.yml index 03e1df306..d654a26bc 100644 --- a/playbooks/roles/tor-bridge/defaults/main.yml +++ b/playbooks/roles/tor-bridge/defaults/main.yml @@ -1,7 +1,7 @@ --- -tor_orport: 8443 -tor_obfs4_port: 9443 - +tor_orport: "{{ lookup('file', lookup('env','HOME') + '/.streisand/tor-orport') or 0 }}" +tor_obfs4_port: "{{ lookup('file', lookup('env','HOME') + '/.streisand/tor-obfs4-port') or 0 }}" +tor_internal_hidden_service_port: 8181 # By default Streisand does *not* publish the Tor relay's service descriptor to # the tor network. Using a value of 0 ensures the relay is private to the # streisand operator and their users. See the Tor documentation[0] for more diff --git a/playbooks/roles/tor-bridge/vars/main.yml b/playbooks/roles/tor-bridge/vars/main.yml index 9ea1e3dbf..1892392ae 100644 --- a/playbooks/roles/tor-bridge/vars/main.yml +++ b/playbooks/roles/tor-bridge/vars/main.yml @@ -18,4 +18,4 @@ tor_html_instructions: "{{ tor_gateway_location }}/index.html" tor_obfs4_qr_code: "{{ tor_gateway_location }}/tor-obfs4-qr-code.png" -tor_internal_hidden_service_address: "127.0.0.1:8181" +tor_internal_hidden_service_address: "127.0.0.1:{{ tor_internal_hidden_service_port }}" diff --git a/playbooks/tor-setup.yml b/playbooks/tor-setup.yml new file mode 100644 index 000000000..b20b0f20d --- /dev/null +++ b/playbooks/tor-setup.yml @@ -0,0 +1,88 @@ +--- +# We randomize the Tor ORPort and obfs4 port fairly early +# in the provisioning process to allow cloud firewalls/playbooks +# to access the tor_orport and tor_obfs4_port variables +# which are chosen randomly. Since we use ansible's "random" filter, +# we run into issues of idempotence if we don't use set_fact to +# make the call to "random". Even if we provide a seed, we +# only get random-but-idempotent behavior within the ansible role. +# Meaning the genesis- roles would have a different number +# for tor_orport and tor_obfs4_port than the number generated when +# the tor-bridge role runs, even if we provide a fixed seed. + +# We randomize Tor's ORPort and obfs4 port to avoid being +# easily fingerprinted as a Tor bridge (or a Streisand Tor bridge) +# See https://lists.torproject.org/pipermail/tor-dev/2014-December/007957.html +# and https://github.com/StreisandEffect/streisand/issues/101 +# for discussion/more data on the topic. +- name: Randomize Tor's ORPort and obfs4 port + hosts: localhost + gather_facts: no + tasks: + - lineinfile: + path: "{{ lookup('env', 'HOME') }}/.streisand/tor-orport" + regexp: "^(.*)$" + state: absent + + - lineinfile: + path: "{{ lookup('env', 'HOME') }}/.streisand/tor-obfs4-port" + regexp: "^(.*)$" + state: absent + + - name: Randomize Tor's ORPort + run_once: true + lineinfile: + path: "{{ lookup('env', 'HOME') }}/.streisand/tor-orport" + regexp: "^[0-9]+$" + create: yes + # Pick a random port from list of available ports + line: "{{ tor_available_ports | random }}" + + - name: Randomize Tor's obfs4 port + run_once: true + lineinfile: + path: "{{ lookup('env', 'HOME') }}/.streisand/tor-obfs4-port" + regexp: "^[0-9]+$" + create: yes + # The "reject" statement is to avoid using the same random port just chosen for tor_orport + line: "{{ tor_available_ports | reject('equalto', 'tor_orport') | list | random }}" + + + vars: + # Keep track of ports already being used by other services to avoid port conflicts + # "map" call is needed here because some of these vars are strings, others are integers + tor_unavailable_ports: "{{ [ + nginx_port, + ssh_port, + le_port, + shadowsocks_server_port, + wireguard_port, + ocserv_port, + openvpn_port, + stunnel_remote_port, + cloudflared_port, + tor_internal_hidden_service_port + ] | map('int') | list }}" + # Choose a random port between 1024-32768--below 1024 would require elevated privileges, + # and any port above 32768 will be used as "Ephemeral Ports" by anything listening locally + # (see output of "cat /proc/sys/net/ipv4/ip_local_port_range" for the full range) + tor_random_port_range: "{{ range(1024, 32768) | list }}" + # Avoid the common ORPort and obfs4 ports + tor_common_ports: [9001, 8443, 9443] + # Calculate available ports by excluding common/used ports from our choices for randomization + tor_available_ports: "{{ tor_random_port_range | difference(tor_common_ports + tor_unavailable_ports) }}" + + # These variable files are included so we can have a list of all + # used ports, this way we can avoid port conflicts when randomizing + # Tor's ORPort and obfs4 port. + vars_files: + - roles/openconnect/defaults/main.yml + - roles/openvpn/defaults/main.yml + - roles/shadowsocks/defaults/main.yml + - roles/ssh/defaults/main.yml + - roles/lets-encrypt/vars/main.yml + - roles/streisand-gateway/defaults/main.yml + - roles/stunnel/defaults/main.yml + - roles/cloudflared/defaults/main.yml + - roles/tor-bridge/defaults/main.yml + - roles/wireguard/defaults/main.yml