From b3b9beddf3ef01365bb42112b5fcd5bd5e593f9d Mon Sep 17 00:00:00 2001 From: Shahrad Elahi Date: Mon, 12 Feb 2024 13:05:46 +0330 Subject: [PATCH] fixes various bugs for Tor service --- README.md | 1 - config/torrc | 4 +- docker-compose.yml | 4 +- docker-entrypoint.sh | 14 +++++- web/src/lib/network.ts | 2 - web/src/lib/wireguard/index.ts | 45 ++++++++++++------- .../api/health/[serviceName]/+server.ts | 10 +++-- .../service/[serviceName]/+page.server.ts | 37 ++++++++++----- .../routes/service/[serviceName]/+page.svelte | 2 +- 9 files changed, 78 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 01dc830..78f7eda 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,6 @@ These options can be configured by setting environment variables using `-e KEY=" |-------------------|----------------------------------------------------------------------------------------------------------------------------------------|----------| | `WG_HOST` | The public IP address of the WireGuard server. | | | `UI_PASSWORD` | The password for the admin UI. | | -| `ORIGIN` | In case you want to access the web-admin remotely, you must set this to the host you are using, for example, `http://:3000`. | ✔️ | | `TOR_USE_BRIDGES` | Set this to `1` and then mount the bridges file at `/etc/torrc.d/bridges.conf`. | ✔️ | | `TOR_*` | The `Torrc` proxy configuration. (e.g. `SocksPort` as `TOR_SOCKSPORT="9050"`) | ✔️ | diff --git a/config/torrc b/config/torrc index 90e9121..9b61dea 100644 --- a/config/torrc +++ b/config/torrc @@ -1,5 +1,5 @@ ##### Auto-Generated by the WireAdmin. Do not edit. ##### VirtualAddrNetwork 10.192.0.0/10 -DNSPort 53530 -TransPort 59040 +DNSPort {{INET_ADDRESS}}:53530 +TransPort {{INET_ADDRESS}}:59040 ClientTransportPlugin obfs4 exec /usr/local/bin/obfs4proxy managed diff --git a/docker-compose.yml b/docker-compose.yml index 9131bf3..02f9527 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,8 +9,8 @@ services: # You can use `openssl rand -base64 8` to generate a secure password - UI_PASSWORD=super-secret-password - image: wireadmin - container_name: litehex/wireadmin + image: litehex/wireadmin + container_name: wireadmin restart: unless-stopped volumes: - persist-data:/data diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index e526de6..0341909 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -4,6 +4,12 @@ set -e TOR_CONFIG="/etc/tor/torrc" ENV_FILE="/app/.env" +log() { + local level=$1 + local message=$2 + echo -e "\n[$(date +"%Y-%m-%d %H:%M:%S")] [$level] $message" +} + to_camel_case() { echo "${1}" | awk -F_ '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2));}1' OFS="" } @@ -12,6 +18,8 @@ generate_tor_config() { # IP address of the container local inet_address="$(hostname -i | awk '{print $1}')" + log "notice" "Using the following IP address for the Tor network: $inet_address" + sed -i "s/{{INET_ADDRESS}}/$inet_address/g" "${TOR_CONFIG}" # any other environment variables that start with TOR_ are added to the torrc @@ -42,6 +50,8 @@ generate_tor_config() { sed -i '/^[^ ]* $/d' "${TOR_CONFIG}" # Remove double empty lines sed -i '/^$/N;/^\n$/D' "${TOR_CONFIG}" + + log "notice" "Tor configuration file has been generated" } echo " " @@ -73,12 +83,12 @@ HASHED_PASSWORD=$(printf "%s" "${UI_PASSWORD}" | od -A n -t x1 | tr -d ' \n') EOF unset UI_PASSWORD else - echo "[error] no password set for the UI" + log "error" "no password set for the UI" exit 1 fi if [ -z "$WG_HOST" ]; then - echo "[error] the WG_HOST environment variable is not set" +log "error" "the WG_HOST environment variable is not set" exit 1 fi diff --git a/web/src/lib/network.ts b/web/src/lib/network.ts index 8882023..5d64021 100644 --- a/web/src/lib/network.ts +++ b/web/src/lib/network.ts @@ -1,5 +1,4 @@ import { execa } from 'execa'; -import logger from '$lib/logger'; import { ip } from 'node-netkit'; export default class Network { @@ -18,7 +17,6 @@ export default class Network { const { stdout: o } = await execa(`ip link show | grep ${inet}`, { shell: true }); return o.trim() !== ''; } catch (e) { - logger.debug('Interface does not exist:', inet); return false; } } diff --git a/web/src/lib/wireguard/index.ts b/web/src/lib/wireguard/index.ts index 0593326..8b663d2 100644 --- a/web/src/lib/wireguard/index.ts +++ b/web/src/lib/wireguard/index.ts @@ -11,6 +11,7 @@ import { sha256 } from '$lib/hash'; import { fsAccess } from '$lib/fs-extra'; import { client } from '$lib/storage'; import { execa } from 'execa'; +import { ip } from 'node-netkit'; export class WGServer { readonly id: string; @@ -32,12 +33,16 @@ export class WGServer { static exists(id: string): boolean { const serverIds = getServers().map((s) => s.id); - logger.debug({ - message: `WGServer:Exists: checking for server with id: ${id}`, - servers: serverIds, - }); + const exists = serverIds.includes(id); - return serverIds.includes(id); + if (!exists) { + logger.debug({ + message: `WGServer: Exists: server by id of ${id} does not exists`, + servers: serverIds, + }); + } + + return exists; } async get(): Promise { @@ -62,11 +67,16 @@ export class WGServer { async stop(): Promise { const server = await this.get(); - if (await Network.interfaceExists(`wg${server.confId}`)) { - await execa(`wg-quick down wg${server.confId}`, { shell: true }); + const iface = `wg${server.confId}`; + + if (await Network.interfaceExists(iface)) { + await execa(`wg-quick down ${iface}`, { shell: true }); } await this.update({ status: 'down' }); + + // TODO: Drop any iptables rules related the interface + return true; } @@ -79,7 +89,6 @@ export class WGServer { } const isAlreadyUp = await this.isUp(); - logger.debug('WGServer:Start: isAlreadyUp:', isAlreadyUp); if (isAlreadyUp) { logger.debug('WGServer:Start: interface already up... taking down'); await execa(`wg-quick down wg${server.confId}`, { shell: true }); @@ -121,9 +130,6 @@ export class WGServer { return true; } - console.log('WGServer:Update: updating server at index:', index); - console.log(client.lrange(WG_SEVER_PATH, 0, -1)); - client.lset( WG_SEVER_PATH, index, @@ -703,15 +709,23 @@ export async function findServer( export async function makeWgIptables(s: WgServer): Promise<{ up: string; down: string }> { const source = `${s.address}/24`; + + const route = await ip.route.defaultRoute(); const wg_inet = `wg${s.confId}`; + if (!route) { + throw new Error('No default route found'); + } + + const { stdout: inet_address } = await execa(`hostname -i | awk '{print $1}'`, { shell: true }); + if (s.tor) { const up = dynaJoin([ `iptables -A INPUT -m state --state ESTABLISHED -j ACCEPT`, `iptables -A INPUT -i ${wg_inet} -s ${source} -m state --state NEW -j ACCEPT`, - `iptables -t nat -A PREROUTING -i ${wg_inet} -p udp -s ${source} --dport 53 -j DNAT --to-destination 127.0.0.1:53530`, - `iptables -t nat -A PREROUTING -i ${wg_inet} -p tcp -s ${source} -j DNAT --to-destination 127.0.0.1:59040`, - `iptables -t nat -A PREROUTING -i ${wg_inet} -p udp -s ${source} -j DNAT --to-destination 127.0.0.1:59040`, + `iptables -t nat -A PREROUTING -i ${wg_inet} -p udp -s ${source} --dport 53 -j DNAT --to-destination ${inet_address}:53530`, + `iptables -t nat -A PREROUTING -i ${wg_inet} -p tcp -s ${source} -j DNAT --to-destination ${inet_address}:59040`, + `iptables -t nat -A PREROUTING -i ${wg_inet} -p udp -s ${source} -j DNAT --to-destination ${inet_address}:59040`, `iptables -t nat -A OUTPUT -o lo -j RETURN`, `iptables -A OUTPUT -m conntrack --ctstate INVALID -j DROP`, `iptables -A OUTPUT -m state --state INVALID -j DROP`, @@ -720,9 +734,8 @@ export async function makeWgIptables(s: WgServer): Promise<{ up: string; down: s return { up, down: up.replace(/-A/g, '-D') }; } - const inet = await Network.defaultInterface(); const up = dynaJoin([ - `iptables -t nat -A POSTROUTING -s ${source} -o ${inet} -j MASQUERADE`, + `iptables -t nat -A POSTROUTING -s ${source} -o ${route.dev} -j MASQUERADE`, `iptables -A INPUT -p udp -m udp --dport ${s.listen} -j ACCEPT`, `iptables -A INPUT -p tcp -m tcp --dport ${s.listen} -j ACCEPT`, `iptables -A FORWARD -i ${wg_inet} -j ACCEPT`, diff --git a/web/src/routes/api/health/[serviceName]/+server.ts b/web/src/routes/api/health/[serviceName]/+server.ts index c8c16e4..c7ab254 100644 --- a/web/src/routes/api/health/[serviceName]/+server.ts +++ b/web/src/routes/api/health/[serviceName]/+server.ts @@ -2,8 +2,12 @@ import { json, type RequestHandler } from '@sveltejs/kit'; import { execa } from 'execa'; export const GET: RequestHandler = async ({ params }) => { - const { stdout } = await execa('screen', ['-ls'], { shell: true }); - const isRunning = stdout.includes(params.serviceName!); + try { + const { stdout } = await execa('screen', ['-ls'], { shell: true }); + const isRunning = stdout.includes(params.serviceName!); - return json({ healthy: isRunning }); + return json({ healthy: isRunning }); + } catch (e) { + return json({ healthy: false }); + } }; diff --git a/web/src/routes/service/[serviceName]/+page.server.ts b/web/src/routes/service/[serviceName]/+page.server.ts index 4a3a3d7..86dda47 100644 --- a/web/src/routes/service/[serviceName]/+page.server.ts +++ b/web/src/routes/service/[serviceName]/+page.server.ts @@ -8,24 +8,18 @@ const services: Record = { tor: { name: 'Tor', command: { - restart: 'screen -L -Logfile /var/vlogs/tor -dmS "tor" tor -f /etc/tor/torrc', + start: 'screen -L -Logfile /var/vlogs/tor -dmS "tor" tor -f /etc/tor/torrc', + stop: 'pkill tor', logs: 'logs tor', }, }, - redis: { - name: 'Redis', - command: { - restart: - 'screen -L -Logfile /var/vlogs/redis -dmS "redis" bash -c "redis-server --port 6479 --daemonize no --dir /data --appendonly yes"', - logs: 'logs redis', - }, - }, }; interface Service { name: string; command: { - restart: string; + start: string; + stop: string; logs: string; }; } @@ -41,6 +35,25 @@ export const load: PageServerLoad = async ({ params }) => { }; }; +function restartService(serviceName: string) { + // Stop + const { exitCode: stopExitCode } = execa(services[serviceName].command.stop, { shell: true }); + + // Start + const { exitCode: startExitCode } = execa(services[serviceName].command.start, { shell: true }); + + logger.info({ + message: `Restarted ${serviceName} service`, + stopExitCode, + startExitCode, + }); + + return { + stopExitCode, + startExitCode, + }; +} + export const actions: Actions = { clearLogs: async ({ request, params }) => { const { serviceName } = params; @@ -68,8 +81,8 @@ export const actions: Actions = { const { serviceName } = params; try { - await execa(services[serviceName!].command.restart, { shell: true }); - return {}; + const { stopExitCode, startExitCode } = restartService(serviceName!); + return { stopExitCode, startExitCode }; } catch (e) { logger.error(e); throw error(500, 'Unhandled Exception'); diff --git a/web/src/routes/service/[serviceName]/+page.svelte b/web/src/routes/service/[serviceName]/+page.svelte index ad3935c..2927ff8 100644 --- a/web/src/routes/service/[serviceName]/+page.svelte +++ b/web/src/routes/service/[serviceName]/+page.svelte @@ -73,7 +73,7 @@ Logs -