commit b784b7c443e324d51c1b0d7e644aebdaf437b01c Author: goyer Date: Wed Nov 9 21:57:34 2022 +0100 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..7f3fe0a --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# CasTor :beaver: + +Tor HTTP proxy with balanced Tor instances. + +![Tor balance schema](docs/tor-balance.png) + +## Requirements + +- [docker](https://docs.docker.com/engine/install/) +- [docker-compose](https://docs.docker.com/compose/install/) + +## Usage + +### Quickstart + +This command will start one Tor instance and HAProxy: + +```bash +docker-compose up +``` + +The HTTP proxy will be running on port **8080**. + +### Start multiple Tor instances + +Use the docker-compose scale option to set the number of Tor instance to start + +For instance to start 5 Tor instances: + +```bash +docker-compose up --scale tor=5 +``` + +### Run proxy on a different port + +Proxy port can be set using the environement varaible `HTTP_PROXY_PORT`. You can specify it in the [`.env`](.env) file or dirrectly in the shell such as: + +```bash +export HTTP_PROXY_PORT=8080 +``` + +## Test the proxy + +Once the application is started, you can test your proxy with `curl`: + +```bash +# test without the proxy +$ curl https://ipinfo.io/ip +A.B.C.D # => your current IP address + +# test with the proxy +$ curl -x http://localhost:8080 https://ipinfo.io/ip +W.X.Y.Z # => Tor exit node IP address +``` + +## How is it working ? + +When you run the command `docker-compose up`, you start at least 3 services (more if you scaled up the number of Tor instances): **tor**, **conf-generator** and **haproxy**. + +The first service started is **tor**, this service is running a Tor instance with a Tor HTTP Tunnel (mandatory to have an HTTP proxy rather than a sock proxy). + +The second service is **conf-generator**, this services runs a python script that does the following things: + +1. get all the IP address of the Tor instances within the docker network (using the Docker API) +2. generate an HAProxy configuration file with the retrieved IPs + +The final service is an HAProxy load balancer using the configuration file generated by **conf-generator**. diff --git a/conf-generator/Dockerfile b/conf-generator/Dockerfile new file mode 100644 index 0000000..6324e40 --- /dev/null +++ b/conf-generator/Dockerfile @@ -0,0 +1,8 @@ +FROM python + +WORKDIR /usr/app/ + +RUN pip install docker jinja2 + +COPY haproxy.j2 /usr/app/haproxy.j2 +COPY gen_conf.py /usr/app/gen_conf.py \ No newline at end of file diff --git a/conf-generator/gen_conf.py b/conf-generator/gen_conf.py new file mode 100644 index 0000000..7c65106 --- /dev/null +++ b/conf-generator/gen_conf.py @@ -0,0 +1,30 @@ +from jinja2 import Template +import docker + + +def get_tor_ips(): + client = docker.DockerClient(base_url='unix://tmp/docker.sock') + network = client.networks.get("net_tor") + net_tor_id = network.attrs["Id"] + + # get the list of containers + containers = client.containers.list() + + containers = [ + container for container in containers + if (container.attrs["NetworkSettings"]["Networks"]["net_tor"]["NetworkID"] == net_tor_id) + and (container.attrs["Config"]["User"] == "tor") + ] + + ip_addrs = [container.attrs['NetworkSettings']['Networks']["net_tor"]["IPAddress"] + for container in containers] + return ip_addrs + + +if __name__ == "__main__": + tor_ips = get_tor_ips() + with open("haproxy.j2", "r") as file: + conf = Template(file.read()).render(tor_hosts=tor_ips) + + with open("/usr/local/etc/haproxy/haproxy.cfg", "w") as file: + file.write(conf) diff --git a/conf-generator/haproxy.j2 b/conf-generator/haproxy.j2 new file mode 100644 index 0000000..31a7dae --- /dev/null +++ b/conf-generator/haproxy.j2 @@ -0,0 +1,25 @@ +global + log stdout format raw local0 info + +defaults + mode http + timeout client 10s + timeout connect 5s + timeout server 10s + timeout http-request 10s + log global + +frontend loadbalancer + bind :80 + mode http + use_backend tors + +backend tors + balance roundrobin + mode http + option forwardfor + {%- for host in tor_hosts %} + server tor{{loop.index}} {{host}}:9080 check + {%- endfor %} + + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2099cea --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,44 @@ +version: "3.6" + +services: + tor: + build: + context: ./tor + dockerfile: Dockerfile + networks: + - net_tor + + haproxy: + image: haproxy:alpine + restart: always + depends_on: + tor: + condition: service_healthy + haproxy-conf-generator: + condition: service_completed_successfully + ports: + - ${HTTP_PROXY_PORT}:80 + volumes: + - haproxy_conf:/usr/local/etc/haproxy + networks: + - net_tor + + haproxy-conf-generator: + build: ./conf-generator + command: python gen_conf.py + depends_on: + tor: + condition: service_healthy + volumes: + - haproxy_conf:/usr/local/etc/haproxy + - /var/run/docker.sock:/tmp/docker.sock + networks: + - net_tor + +volumes: + haproxy_conf: + + +networks: + net_tor: + name: net_tor diff --git a/docs/tor-balance.png b/docs/tor-balance.png new file mode 100644 index 0000000..add4bd0 Binary files /dev/null and b/docs/tor-balance.png differ diff --git a/tor/Dockerfile b/tor/Dockerfile new file mode 100644 index 0000000..aca258e --- /dev/null +++ b/tor/Dockerfile @@ -0,0 +1,3 @@ +FROM osminogin/tor-simple + +COPY torrc /etc/tor/torrc \ No newline at end of file diff --git a/tor/torrc b/tor/torrc new file mode 100644 index 0000000..7609be7 --- /dev/null +++ b/tor/torrc @@ -0,0 +1,249 @@ +SocksPort 0.0.0.0:9050 +## Configuration file for a typical Tor user +## Last updated 28 February 2019 for Tor 0.3.5.1-alpha. +## (may or may not work for much older or much newer versions of Tor.) +## +## Lines that begin with "## " try to explain what's going on. Lines +## that begin with just "#" are disabled commands: you can enable them +## by removing the "#" symbol. +## +## See 'man tor', or https://www.torproject.org/docs/tor-manual.html, +## for more options you can use in this file. +## +## Tor will look for this file in various places based on your platform: +## https://www.torproject.org/docs/faq#torrc + +## Tor opens a SOCKS proxy on port 9050 by default -- even if you don't +## configure one below. Set "SOCKSPort 0" if you plan to run Tor only +## as a relay, and not make any local application connections yourself. +#SOCKSPort 9050 # Default: Bind to localhost:9050 for local connections. +#SOCKSPort 192.168.0.1:9100 # Bind to this address:port too. + +## Entry policies to allow/deny SOCKS requests based on IP address. +## First entry that matches wins. If no SOCKSPolicy is set, we accept +## all (and only) requests that reach a SOCKSPort. Untrusted users who +## can access your SOCKSPort may be able to learn about the connections +## you make. +#SOCKSPolicy accept 192.168.0.0/16 +#SOCKSPolicy accept6 FC00::/7 +#SOCKSPolicy reject * + +## Logs go to stdout at level "notice" unless redirected by something +## else, like one of the below lines. You can have as many Log lines as +## you want. +## +## We advise using "notice" in most cases, since anything more verbose +## may provide sensitive information to an attacker who obtains the logs. +## +## Send all messages of level 'notice' or higher to /var/log/tor/notices.log +Log notice file /var/log/tor/notices.log +## Send every possible message to /var/log/tor/debug.log +#Log debug file /var/log/tor/debug.log +## Use the system log instead of Tor's logfiles +#Log notice syslog +## To send all messages to stderr: +#Log debug stderr + +## The directory for keeping all the keys/etc. By default, we store +## things in $HOME/.tor on Unix, and in Application Data\tor on Windows. +DataDirectory /var/lib/tor + +## The port on which Tor will listen for local connections from Tor +## controller applications, as documented in control-spec.txt. +#ControlPort 9051 +## If you enable the controlport, be sure to enable one of these +## authentication methods, to prevent attackers from accessing it. +#HashedControlPassword 16:872860B76453A77D60CA2BB8C1A7042072093276A3D701AD684053EC4C +#CookieAuthentication 1 + +############### This section is just for location-hidden services ### + +## Once you have configured a hidden service, you can look at the +## contents of the file ".../hidden_service/hostname" for the address +## to tell people. +## +## HiddenServicePort x y:z says to redirect requests on port x to the +## address y:z. + +#HiddenServiceDir /var/lib/tor/hidden_service/ +#HiddenServicePort 80 127.0.0.1:80 + +#HiddenServiceDir /var/lib/tor/other_hidden_service/ +#HiddenServicePort 80 127.0.0.1:80 +#HiddenServicePort 22 127.0.0.1:22 + +################ This section is just for relays ##################### +# +## See https://www.torproject.org/docs/tor-doc-relay for details. + +## Required: what port to advertise for incoming Tor connections. +#ORPort 9001 +## If you want to listen on a port other than the one advertised in +## ORPort (e.g. to advertise 443 but bind to 9090), you can do it as +## follows. You'll need to do ipchains or other port forwarding +## yourself to make this work. +#ORPort 443 NoListen +#ORPort 127.0.0.1:9090 NoAdvertise +## If you want to listen on IPv6 your numeric address must be explicitly +## between square brackets as follows. You must also listen on IPv4. +#ORPort [2001:DB8::1]:9050 + +## The IP address or full DNS name for incoming connections to your +## relay. Leave commented out and Tor will guess. +#Address noname.example.com + +## If you have multiple network interfaces, you can specify one for +## outgoing traffic to use. +## OutboundBindAddressExit will be used for all exit traffic, while +## OutboundBindAddressOR will be used for all OR and Dir connections +## (DNS connections ignore OutboundBindAddress). +## If you do not wish to differentiate, use OutboundBindAddress to +## specify the same address for both in a single line. +#OutboundBindAddressExit 10.0.0.4 +#OutboundBindAddressOR 10.0.0.5 + +## A handle for your relay, so people don't have to refer to it by key. +## Nicknames must be between 1 and 19 characters inclusive, and must +## contain only the characters [a-zA-Z0-9]. +## If not set, "Unnamed" will be used. +#Nickname ididnteditheconfig + +## Define these to limit how much relayed traffic you will allow. Your +## own traffic is still unthrottled. Note that RelayBandwidthRate must +## be at least 75 kilobytes per second. +## Note that units for these config options are bytes (per second), not +## bits (per second), and that prefixes are binary prefixes, i.e. 2^10, +## 2^20, etc. +#RelayBandwidthRate 100 KBytes # Throttle traffic to 100KB/s (800Kbps) +#RelayBandwidthBurst 200 KBytes # But allow bursts up to 200KB (1600Kb) + +## Use these to restrict the maximum traffic per day, week, or month. +## Note that this threshold applies separately to sent and received bytes, +## not to their sum: setting "40 GB" may allow up to 80 GB total before +## hibernating. +## +## Set a maximum of 40 gigabytes each way per period. +#AccountingMax 40 GBytes +## Each period starts daily at midnight (AccountingMax is per day) +#AccountingStart day 00:00 +## Each period starts on the 3rd of the month at 15:00 (AccountingMax +## is per month) +#AccountingStart month 3 15:00 + +## Administrative contact information for this relay or bridge. This line +## can be used to contact you if your relay or bridge is misconfigured or +## something else goes wrong. Note that we archive and publish all +## descriptors containing these lines and that Google indexes them, so +## spammers might also collect them. You may want to obscure the fact that +## it's an email address and/or generate a new address for this purpose. +## +## If you are running multiple relays, you MUST set this option. +## +#ContactInfo Random Person +## You might also include your PGP or GPG fingerprint if you have one: +#ContactInfo 0xFFFFFFFF Random Person + +## Uncomment this to mirror directory information for others. Please do +## if you have enough bandwidth. +#DirPort 9030 # what port to advertise for directory connections +## If you want to listen on a port other than the one advertised in +## DirPort (e.g. to advertise 80 but bind to 9091), you can do it as +## follows. below too. You'll need to do ipchains or other port +## forwarding yourself to make this work. +#DirPort 80 NoListen +#DirPort 127.0.0.1:9091 NoAdvertise +## Uncomment to return an arbitrary blob of html on your DirPort. Now you +## can explain what Tor is if anybody wonders why your IP address is +## contacting them. See contrib/tor-exit-notice.html in Tor's source +## distribution for a sample. +#DirPortFrontPage /etc/tor/tor-exit-notice.html + +## Uncomment this if you run more than one Tor relay, and add the identity +## key fingerprint of each Tor relay you control, even if they're on +## different networks. You declare it here so Tor clients can avoid +## using more than one of your relays in a single circuit. See +## https://www.torproject.org/docs/faq#MultipleRelays +## However, you should never include a bridge's fingerprint here, as it would +## break its concealability and potentially reveal its IP/TCP address. +## +## If you are running multiple relays, you MUST set this option. +## +## Note: do not use MyFamily on bridge relays. +#MyFamily $keyid,$keyid,... + +## Uncomment this if you want your relay to be an exit, with the default +## exit policy (or whatever exit policy you set below). +## (If ReducedExitPolicy, ExitPolicy, or IPv6Exit are set, relays are exits. +## If none of these options are set, relays are non-exits.) +#ExitRelay 1 + +## Uncomment this if you want your relay to allow IPv6 exit traffic. +## (Relays do not allow any exit traffic by default.) +#IPv6Exit 1 + +## Uncomment this if you want your relay to be an exit, with a reduced set +## of exit ports. +#ReducedExitPolicy 1 + +## Uncomment these lines if you want your relay to be an exit, with the +## specified set of exit IPs and ports. +## +## A comma-separated list of exit policies. They're considered first +## to last, and the first match wins. +## +## If you want to allow the same ports on IPv4 and IPv6, write your rules +## using accept/reject *. If you want to allow different ports on IPv4 and +## IPv6, write your IPv6 rules using accept6/reject6 *6, and your IPv4 rules +## using accept/reject *4. +## +## If you want to _replace_ the default exit policy, end this with either a +## reject *:* or an accept *:*. Otherwise, you're _augmenting_ (prepending to) +## the default exit policy. Leave commented to just use the default, which is +## described in the man page or at +## https://www.torproject.org/documentation.html +## +## Look at https://www.torproject.org/faq-abuse.html#TypicalAbuses +## for issues you might encounter if you use the default exit policy. +## +## If certain IPs and ports are blocked externally, e.g. by your firewall, +## you should update your exit policy to reflect this -- otherwise Tor +## users will be told that those destinations are down. +## +## For security, by default Tor rejects connections to private (local) +## networks, including to the configured primary public IPv4 and IPv6 addresses, +## and any public IPv4 and IPv6 addresses on any interface on the relay. +## See the man page entry for ExitPolicyRejectPrivate if you want to allow +## "exit enclaving". +## +#ExitPolicy accept *:6660-6667,reject *:* # allow irc ports on IPv4 and IPv6 but no more +#ExitPolicy accept *:119 # accept nntp ports on IPv4 and IPv6 as well as default exit policy +#ExitPolicy accept *4:119 # accept nntp ports on IPv4 only as well as default exit policy +#ExitPolicy accept6 *6:119 # accept nntp ports on IPv6 only as well as default exit policy +#ExitPolicy reject *:* # no exits allowed + +## Bridge relays (or "bridges") are Tor relays that aren't listed in the +## main directory. Since there is no complete public list of them, even an +## ISP that filters connections to all the known Tor relays probably +## won't be able to block all the bridges. Also, websites won't treat you +## differently because they won't know you're running Tor. If you can +## be a real relay, please do; but if not, be a bridge! +## +## Warning: when running your Tor as a bridge, make sure than MyFamily is +## NOT configured. +#BridgeRelay 1 +## By default, Tor will advertise your bridge to users through various +## mechanisms like https://bridges.torproject.org/. If you want to run +## a private bridge, for example because you'll give out your bridge +## address manually to your friends, uncomment this line: +#BridgeDistribution none + +## Configuration options can be imported from files or folders using the %include +## option with the value being a path. This path can have wildcards. Wildcards are +## expanded first, using lexical order. Then, for each matching file or folder, the following +## rules are followed: if the path is a file, the options from the file will be parsed as if +## they were written where the %include option is. If the path is a folder, all files on that +## folder will be parsed following lexical order. Files starting with a dot are ignored. Files +## on subfolders are ignored. +## The %include option can be used recursively. +#%include /etc/torrc.d/*.conf +HTTPTunnelPort 0.0.0.0:9080 \ No newline at end of file