diff --git a/.dockerignore b/.dockerignore index b45826ec..27001b70 100755 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,6 @@ onionr/data/**/* onionr/data MY-RUN.sh +Dockerfile +.dockerignore +.git diff --git a/Dockerfile b/Dockerfile index c8e93527..12953ac1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,12 @@ FROM python:3.7 +EXPOSE 8080 USER root RUN mkdir /app WORKDIR /app -ENV PORT=8080 -EXPOSE 8080 +ENV ONIONR_DOCKER=true #Install needed packages RUN apt-get update && apt-get install -y tor locales @@ -26,5 +26,6 @@ VOLUME /app/data/ #Default to running as nonprivileged user RUN chmod g=u -R /app USER 1000 +ENV HOME=/app -CMD ["bash", "./onionr.sh"] +CMD ["bash", "./run-onionr-node.sh"] diff --git a/docs/dev/selected-papers.md b/docs/dev/selected-papers.md new file mode 100644 index 00000000..52bc6e25 --- /dev/null +++ b/docs/dev/selected-papers.md @@ -0,0 +1,9 @@ +# Interesting papers related to Onionr development + +A paper being listed here is not end-all-be-all endorsement of every detail inside. + +* [Epidemic Routing for Partially-Connected Ad Hoc Networks](https://web.archive.org/web/20200208074703/http://issg.cs.duke.edu/epidemic/epidemic.pdf) +* [Freenet: A distibuted decentralized information storage and retrieval system](https://freenetproject.org/assets/papers/ddisrs.pdf) +* [Protecting Free Expression Online with Freenet](https://freenetproject.org/assets/papers/ddisrs.pdf) +* [Bitmessage: A Peer‐to‐Peer Message Authentication and Delivery System](https://archive.org/details/BitmessageWhitepaper/) +* [MuON: Epidemic based Mutual Anonymity](https://web.archive.org/web/20060901153544/http://www.csl.mtu.edu/cs6461/www/Reading/MuON_ICNP2005.pdf) diff --git a/docs/docker.md b/docs/docker.md new file mode 100644 index 00000000..f350bd3d --- /dev/null +++ b/docs/docker.md @@ -0,0 +1,28 @@ +# Running Onionr in Docker + +A Dockerfile is included in the root directory of Onionr. + +In Docker version 20.10 (and probably others), there is a strange bug where Onionr must be run with -it or stdout will be garbled and it may hang. + +## Clone and build the image + +`$ git clone https://git.voidnet.tech/kev/onionr/` +`$ cd onionr` +`$ sudo docker build -t onionr .` + + +## Run Onionr + +`$ sudo docker run -it -p 8080:8080 onionr` + +Onionr will be accessible over any network interface by default, so make sure to either change the entry point bind-address argument or set a firewall rule. + +That said, Onionr does protect it's interface by default with a web token, which will be shown in stdout. + +**However, anyone who can access the port may be able to see what Onionr sites you have saved and potentially deanonymize your node** + +## View the UI + +Visit the address and port for the machine Onionr is running on, for example: http://192.168.1.5:8080/# + +If you want a secure connection to the interface, either use a proxy such as nginx or caddy, or use [SSH tunneling](./vps-cloud-guide.md). diff --git a/docs/vps-cloud-guide.md b/docs/vps-cloud-guide.md new file mode 100644 index 00000000..ff931f46 --- /dev/null +++ b/docs/vps-cloud-guide.md @@ -0,0 +1,18 @@ +# Cloud/Server Hosting Onionr + +Cloud hosting is not the recommended way to run Onionr, as it gives the cloud provider control over your data. + +That said, it is quite useful and by running a 24/7 Onionr node you contribute to the network's health. + +![https://cock.li/img/cockbox.png](https://cockbox.org/?r=12) + +[Cockbox](https://cockbox.org/?r=12) is the recommended provider, as they do not demand personal information and accept Monero. This is an affiliate link, which gives us a commission at no expense or complication to you. + + +1. [Install Onionr like normal](./basic-onionr-user-guide.pdf) or [from Docker](./docker.md) +2. Run with specific host and port to bind to: `$ ./run-onionr-node.py --bind-address 0.0.0.0 --port 8080` +3. Get the web security token: `$ ./onionr.sh get-web` (the long string at the end of the URL) +4. [Configure Firefox for SSH tunneling](https://web.archive.org/web/20210123034529/https://gist.github.com/brentjanderson/6ed800376e53746d2d28ba7b6bdcdc12). `Set network.proxy.allow_hijacking_localhost` to true in about:config + + +Disable random host binding in config (general.security.random_bind_ip = false) if you have Onionr auto start. You may also want to disable deniable block inserts. \ No newline at end of file diff --git a/run-onionr-node.py b/run-onionr-node.py index 15fd581e..2419c72f 100755 --- a/run-onionr-node.py +++ b/run-onionr-node.py @@ -9,10 +9,8 @@ from subprocess import DEVNULL import ujson from psutil import Popen from psutil import Process -import psutil import sys -import curses script_dir = os.path.dirname(os.path.realpath(__file__)) sys.path.append(script_dir + '/src/') @@ -23,36 +21,6 @@ from etc import onionrvalues sub_script = script_dir + '/' + onionrvalues.SCRIPT_NAME -def show_info(p: Process): - def pbar(window): - window.addstr(8, 10, "Onionr statistics") - window.addstr(9, 10, "-" * 17) - curses.curs_set(0) - while True: - threads = p.num_threads() - open_files = len(p.open_files()) - cpu_percent = p.cpu_percent() - block_count = len(blockmetadb.get_block_list()) - for proc in p.children(recursive=True): - threads += proc.num_threads() - cpu_percent += proc.cpu_percent() - try: - open_files += len(proc.open_files()) - except psutil.AccessDenied: - pass - cpu_percent = cpu_percent * 100 - window.addstr(11, 10, f"Threads: {threads}") - window.addstr(10, 10, f"Open files: {open_files}") - window.addstr(12, 10, f"CPU: {cpu_percent}%") - window.addstr(13, 10, f"Blocks: {block_count}") - window.refresh() - sleep(0.5) - sleep(15) - curses.wrapper(pbar) - while True: - sleep(1) - - parser = argparse.ArgumentParser() parser.add_argument( @@ -111,8 +79,9 @@ parser.add_argument( args = parser.parse_args() -p = Popen([sub_script, 'version'], stdout=DEVNULL) +p = Popen([sub_script, 'version']) p.wait() +print("Configuring Onionr before starting daemon") from filepaths import config_file, keys_file from coredb import blockmetadb import onionrcrypto @@ -172,13 +141,10 @@ with open(config_file, 'w') as cf: cf.write(ujson.dumps(config, reject_bytes=False)) if args.open_ui: - p = Popen([sub_script, 'start'], stdout=DEVNULL) + p = Popen([sub_script, 'start']) sleep(2) - Popen([sub_script, 'openhome'], stdout=DEVNULL) + Popen([sub_script, 'openhome']) else: - p = Popen([sub_script, 'start'], stdout=DEVNULL) + p = Popen([sub_script, 'start']) -p = p.children()[0] -if args.show_stats: - Thread(target=show_info, args=[p], daemon=True).start() p.wait() diff --git a/run-onionr-node.sh b/run-onionr-node.sh new file mode 100755 index 00000000..42f37565 --- /dev/null +++ b/run-onionr-node.sh @@ -0,0 +1,51 @@ +#!/bin/sh +set -x +ORIG_ONIONR_RUN_DIR=`pwd` +export ORIG_ONIONR_RUN_DIR +cd "$(dirname "$0")" + +if [[ -n "$ONIONR_DOCKER" ]]; then + [[ -f "/privkey" ]] && privkey_opt="--private-key /privkey" + [[ -n "$ONIONR_ONBOARDING" ]] || ONIONR_ONBOARDING=0 + [[ -n "$ONIONR_OPEN_UI" ]] || ONIONR_OPEN_UI=0 + [[ -n "$ONIONR_RANDOM_LOCALHOST_IP" ]] || ONIONR_RANDOM_LOCALHOST_IP=0 + [[ -n "$ONIONR_BIND_ADDRESS" ]] || ONIONR_BIND_ADDRESS=0.0.0.0 + [[ -n "$ONIONR_PORT" ]] || ONIONR_PORT=8080 +fi + +[[ -n "$ONIONR_PRIVATE_KEY_FILE" ]] && privkey_opt="--private-key $ONIONR_PRIVATE_KEY_FILE" +[[ -n "$ONIONR_USE_BOOTSTRAP_FILE" ]] && bootstrap_opt="--use-bootstrap-file $ONIONR_USE_BOOTSTRAP_FILE" +[[ -n "$ONIONR_SHOW_STATS" ]] && show_stats_opt="--show-stats $ONIONR_SHOW_STATS" +[[ -n "$ONIONR_ONBOARDING" ]] && onboarding_opt="--onboarding $ONIONR_ONBOARDING" +[[ -n "$ONIONR_SECURITY_LEVEL" ]] && security_level_opt="--security-level $ONIONR_SECURITY_LEVEL" +[[ -n "$ONIONR_OPEN_UI" ]] && open_ui_opt="--open-ui $ONIONR_OPEN_UI" +[[ -n "$ONIONR_RANDOM_LOCALHOST_IP" ]] && random_localhost_ip_opt="--random-localhost-ip $ONIONR_RANDOM_LOCALHOST_IP" +[[ -n "$ONIONR_USE_TOR" ]] && use_tor_opt="--use-tor $ONIONR_USE_TOR" +[[ -n "$ONIONR_ANIMATED_BACKGROUND" ]] && animated_background_opt="--animated-background $ONIONR_ANIMATED_BACKGROUND" +[[ -n "$ONIONR_KEEP_LOG" ]] && keep_log_opt="--keep-log-on-exit $ONIONR_KEEP_LOG" +[[ -n "$ONIONR_USE_UPLOAD_MIXING" ]] && use_upload_mixing_opt="--use-upload-mixing $ONIONR_USE_UPLOAD_MIXING" +[[ -n "$ONIONR_DEV_MODE" ]] && dev_mode_opt="--dev-mode $ONIONR_DEV_MODE" +[[ -n "$ONIONR_DISABLE_PLUGIN_LIST" ]] && disable_plugin_list_opt=" --disable-plugin-list $ONIONR_DISABLE_PLUGIN_LIST" +[[ -n "$ONIONR_STORE_PLAINTEXT" ]] && store_plaintext_opt="--store-plaintext $ONIONR_STORE_PLAINTEXT" +[[ -n "$ONIONR_BIND_ADDRESS" ]] && bind_address_opt="--bind-address $ONIONR_BIND_ADDRESS" +[[ -n "$ONIONR_PORT" ]] && port_opt="--port $ONIONR_PORT" + + +python3 run-onionr-node.py \ + $privkey_opt \ + $bootstrap_opt \ + $show_stats_opt \ + $onboarding_opt \ + $security_level_opt \ + $open_ui_opt \ + $random_localhost_ip_opt \ + $use_tor_opt \ + $animated_background_opt \ + $keep_log_opt \ + $use_upload_mixing_opt \ + $dev_mode_opt \ + $disable_plugin_list_opt \ + $store_plaintext_opt \ + $bind_address_opt \ + $port_opt \ + "$@" diff --git a/scripts/remote-ui.py b/scripts/remote-ui.py new file mode 100755 index 00000000..b63540c3 --- /dev/null +++ b/scripts/remote-ui.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +"""Craft and send requests to the local client API""" + + +import sys +import os +import time +from threading import Thread +if not os.path.exists('onionr.sh'): + os.chdir('../') +sys.path.append("src/") + +import filepaths +import config +config.reload() + +with open(filepaths.private_API_host_file, 'r') as host: + hostname = host.read() + +port = config.get("client.client.port", 0) +if not port: + print("Could not get port for Onionr UI. Try again") + sys.exit(1) +torrc = f""" +HiddenServiceDir remote-onionr-hs +HiddenServicePort 80 {hostname}:{port} +""" + +with open("remote-onionr-torrc", "w") as torrc_f: + torrc_f.write(torrc) + + +def show_onion(): + while True: + time.sleep(1) + try: + with open("remote-onionr-hs/hostname", "r") as f: + o = f.read() + print("UI Onion (Keep secret):", o) + config.set("ui.public_remote_enabled", True) + config.set("ui.public_remote_hosts", [o]) + config.save() + break + except FileNotFoundError: + pass + +Thread(target=show_onion, daemon=True).start() + +os.system("tor -f remote-onionr-torrc") diff --git a/src/httpapi/security/client.py b/src/httpapi/security/client.py index 11ee4f99..716e9295 100644 --- a/src/httpapi/security/client.py +++ b/src/httpapi/security/client.py @@ -3,6 +3,7 @@ Process incoming requests to the client api server to validate that they are legitimate and not DNSR/XSRF or other local adversary """ +from ipaddress import ip_address import hmac from flask import Blueprint, request, abort, g @@ -53,21 +54,22 @@ class ClientAPISecurity: def validate_request(): """Validate request has set password & is the correct hostname.""" # For the purpose of preventing DNS rebinding attacks - localhost = True - if request.host != '%s:%s' % \ - (client_api.host, client_api.bindPort): - localhost = False + if ip_address(client_api.host).is_loopback: + localhost = True + if request.host != '%s:%s' % \ + (client_api.host, client_api.bindPort): + localhost = False - if not localhost and public_remote_enabled: - if request.host not in public_remote_hostnames: - logger.warn( - f'{request.host} not in {public_remote_hostnames}') - abort(403) - else: - if not localhost: - logger.warn( - f'Possible DNS rebinding attack by {request.host}') - abort(403) + if not localhost and public_remote_enabled: + if request.host not in public_remote_hostnames: + logger.warn( + f'{request.host} not in {public_remote_hostnames}') + abort(403) + else: + if not localhost: + logger.warn( + f'Possible DNS rebinding attack by {request.host}') + abort(403) # Add shared objects try: diff --git a/src/netcontroller/torcontrol/__init__.py b/src/netcontroller/torcontrol/__init__.py index d1b8b3c9..a5e57495 100644 --- a/src/netcontroller/torcontrol/__init__.py +++ b/src/netcontroller/torcontrol/__init__.py @@ -91,15 +91,17 @@ class NetController: if '100' not in line.decode(): logger.info(line.decode().strip(), terminal=True) if 'bootstrapped 100' in line.decode().lower(): - logger.info(line.decode()) + logger.info(line.decode(), terminal=True) break elif 'opening socks listener' in line.decode().lower(): logger.debug(line.decode().replace('\n', '')) else: if 'err' in line.decode(): - logger.error(line.decode().replace('\n', '')) + logger.error( + line.decode().replace('\n', ''), terminal=True) elif 'warn' in line.decode(): - logger.warn(line.decode().replace('\n', '')) + logger.warn( + line.decode().replace('\n', ''), terminal=True) else: logger.debug(line.decode().replace('\n', '')) else: @@ -119,8 +121,8 @@ class NetController: with open(self.dataDir + 'torPid.txt', 'w') as tor_pid_file: tor_pid_file.write(str(tor.pid)) - multiprocessing.Process(target=watchdog.watchdog, - args=[os.getpid(), tor.pid], daemon=True).start() + #multiprocessing.Process(target=watchdog.watchdog, + # args=[os.getpid(), tor.pid], daemon=True).start() logger.info('Finished starting Tor.', terminal=True) diff --git a/src/utils/createdirs.py b/src/utils/createdirs.py index f819800b..c5d7fe52 100644 --- a/src/utils/createdirs.py +++ b/src/utils/createdirs.py @@ -4,8 +4,6 @@ Create required Onionr directories """ import os import stat -from pwd import getpwuid -from getpass import getuser from . import identifyhome import filepaths @@ -27,10 +25,6 @@ import onionrexceptions home = identifyhome.identify_home() -def find_owner(filename): - return getpwuid(os.stat(filename).st_uid).pw_name - - def create_dirs(): """Create onionr data-related directories in order of the hardcoded list below, @@ -42,7 +36,7 @@ def create_dirs(): if not os.path.exists(path): os.makedirs(path) else: - if getuser() != find_owner(path): + if os.getuid() != os.stat(path).st_uid: raise onionrexceptions.InsecureDirectoryUsage( "Directory " + path + " already exists and is not owned by the same user") @@ -55,4 +49,4 @@ def create_dirs(): try: db() except FileExistsError: - pass \ No newline at end of file + pass diff --git a/tests/runtime-result.txt b/tests/runtime-result.txt index e1b6872c..5b6a44e2 100644 --- a/tests/runtime-result.txt +++ b/tests/runtime-result.txt @@ -1 +1 @@ -1610736254 \ No newline at end of file +1611429331 \ No newline at end of file