IRC Server as Tor Hidden Service on OpenBSD
A brief guide on how to set up an IRC server (Ergo) as a Tor v3 Onion Hidden Service on OpenBSD for secret idle parties with your friends.
With increasing surveillance by companies as well as government services, many online services that were once fun to use have become a potential threat for one’s privacy. Not only e-mail accounts and social networking websites, but also niche services like the IRC cannot blindly be trusted anymore.
Therefore let’s take a look at how you can set up your own IRC server for you and your friends, in a way that allows you to freely communication without being bothered by big tech or anyone else. We’re going to be using Ergo for this, as it’s an up to date IRC server written in Go that offers bleeding-edge IRCv3 support. We’re also going to use OpenBSD for the base system.
Preparation
Usually when I build OpenBSD-based infrastructure, I use a Vultr VPS instance. Even though Vultr allows running Tor on their service unless it’s an exit node, for this setup I’d rather take a look at a different infrastructure provider that is more focused on privacy and ideally accepts payments via XMR.
Note: I won’t explicitly mention under which user I’m running each command, hence please read carefully. When the prompt shows
ircd#
, I’m acting asroot
user, otherwise, when it showsircd%
, I’m the using the_ergo
user.
As soon as the OpenBSD VPS has booted, we can log in via SSH and perform a quick update of the system:
ircd# syspatch
ircd# pkg_add -u
After that, let’s begin by installing some handy tools:
ircd# pkg_add git zsh neovim wget mosh rsync htop
What I like to do is link nvim
to vim
, because typing vim
is in my muscle
memory. I also like to use zsh
as shell. Since that’s all preference, these
steps are optional:
ircd# ln -s /usr/local/bin/nvim /usr/local/bin/vim
ircd# chsh -s /usr/local/bin/zsh root
Another optional but useful thing that I like to do is to change the SSH port
and disable password authentication / enable pubkey authentication. Make sure
you have an authorized_keys
entry with your pubkey in place before applying
this change:
ircd# sed -i 's/^#Port 22/Port 31231/g;\
s/^#PubkeyAuthentication .*/PubkeyAuthentication yes/g;\
s/^#PasswordAuthentication .*/PasswordAuthentication no/g' \
/etc/ssh/sshd_config
ircd# rcctl restart sshd
Afterwards, disconnect from SSH, and re-connect, ideally using mosh
, and
launch tmux
for the sake of comfort.
Tor
We begin by installing and configuring the Tor hidden service:
ircd# pkg_add tor
ircd# cat /etc/tor/torrc
Log notice syslog
RunAsDaemon 1
DataDirectory /var/tor
HiddenServiceDir /var/tor/hidden_service/
HiddenServicePort 6667 127.0.0.1:6667 unix:/hidden_service_sockets/ergo_tor_sock
User _tor
Next, enable the Tor hidden service:
ircd# rcctl enable tor
ircd# rcctl start tor
We can now check the hidden service’s Onion address:
ircd# cat /var/tor/hidden_service/hostname
xyz.onion
This is the address that our hidden service IRC server will be available at. We will need to add this address to Ergo’s configuration.
Ergo IRCd
First download and install the latest OpenBSD release of Ergo from
GitHub – 2.10.0
as of writing this:
ircd# wget https://github.com/ergochat/ergo/releases/download/v2.10.0/ergo-2.10.0-openbsd-x86_64.tar.gz
ircd# tar -xzf ergo-2.10.0-openbsd-x86_64.tar.gz
ircd# cp ergo-2.10.0-openbsd-x86_64/ergo /usr/local/bin/ergo
Next, add a new system user for Ergo:
ircd# groupadd _ergo
ircd# useradd -d /home/_ergo -m -c "Ergo" -g _ergo -L daemon -s /sbin/nologin _ergo
Now we can create a configuration as well as a MOTD file for Ergo in /etc/
:
ircd# cat /etc/ircd.yaml
network:
name: my-ircd
server:
name: xyz.onion
listeners:
"127.0.0.1:6667": # (loopback ipv4, localhost-only)
"/hidden_service_sockets/ergo_tor_sock":
tor: true
unix-bind-mode: 0777
tor-listeners:
require-sasl: false
vhost: "xyz.onion"
max-connections: 1024
throttle-duration: 10m
max-connections-per-duration: 64
sts:
enabled: false
casemapping: "ascii"
enforce-utf8: true
lookup-hostnames: false
forward-confirm-hostnames: false
check-ident: false
coerce-ident: '~u'
motd: /etc/ircd.motd
motd-formatting: false
relaymsg:
enabled: false
proxy-allowed-from:
- localhost
max-sendq: 96k
compatibility:
force-trailing: true
send-unprefixed-sasl: true
allow-truncation: false
ip-limits:
count: true
max-concurrent-connections: 64
throttle: true
window: 10m
max-connections-per-window: 32
cidr-len-ipv4: 32
cidr-len-ipv6: 64
exempted:
- localhost
custom-limits:
#"irccloud":
# nets:
# - "192.184.9.108" # highgate.irccloud.com
# - "192.184.9.110" # ealing.irccloud.com
# - "192.184.9.112" # charlton.irccloud.com
# - "192.184.10.118" # brockwell.irccloud.com
# - "192.184.10.9" # tooting.irccloud.com
# - "192.184.8.73" # hathersage.irccloud.com
# - "192.184.8.103" # stonehaven.irccloud.com
# - "5.254.36.57" # tinside.irccloud.com
# - "5.254.36.56/29" # additional ipv4 net
# - "2001:67c:2f08::/48"
# - "2a03:5180:f::/64"
# max-concurrent-connections: 2048
# max-connections-per-window: 2048
ip-check-script:
enabled: false
command: "/usr/local/bin/check-ip-ban"
args: []
timeout: 9s
kill-timeout: 1s
max-concurrency: 64
exempt-sasl: false
ip-cloaking:
enabled: true
enabled-for-always-on: true
netname: "my-ircd"
cidr-len-ipv4: 32
cidr-len-ipv6: 64
num-bits: 64
secure-nets:
# - "10.0.0.0/8"
suppress-lusers: false
accounts:
authentication-enabled: true
registration:
enabled: true
allow-before-connect: true
throttling:
enabled: true
duration: 10m
max-attempts: 30
bcrypt-cost: 4
verify-timeout: "32h"
email-verification:
enabled: false
password-reset:
enabled: false
login-throttling:
enabled: true
duration: 1m
max-attempts: 3
skip-server-password: false
login-via-pass-command: true
require-sasl:
enabled: false
exempted:
- "localhost"
# - '10.10.0.0/16'
nick-reservation:
enabled: true
additional-nick-limit: 1
method: strict
allow-custom-enforcement: false
guest-nickname-format: "Privateer-*"
force-guest-format: false
force-nick-equals-account: false
forbid-anonymous-nick-changes: false
multiclient:
enabled: false
vhosts:
enabled: true
max-length: 64
valid-regexp: '^[0-9A-Za-z.\-_/]+$'
default-user-modes: +i
auth-script:
enabled: false
channels:
default-modes: +ntC
max-channels-per-client: 100
operator-only-creation: false
registration:
enabled: true
operator-only: false
max-channels-per-account: 15
list-delay: 60s
invite-expiration: 24h
oper-classes:
"chat-moderator":
title: Moderator
capabilities:
- "kill" # disconnect user sessions
- "ban" # ban IPs, CIDRs, NUH masks, and suspend accounts (UBAN / DLINE / KLINE)
- "nofakelag" # exempted from "fakelag" restrictions on rate of message sending
- "relaymsg" # use RELAYMSG in any channel (see the `relaymsg` config block)
- "vhosts" # add and remove vhosts from users
- "sajoin" # join arbitrary channels, including private channels
- "samode" # modify arbitrary channel and user modes
- "snomasks" # subscribe to arbitrary server notice masks
- "roleplay" # use the (deprecated) roleplay commands in any channel
"server-admin":
title: Admin
extends: "chat-moderator"
capabilities:
- "rehash" # rehash the server, i.e. reload the config at runtime
- "accreg" # modify arbitrary account registrations
- "chanreg" # modify arbitrary channel registrations
- "history" # modify or delete history messages
- "defcon" # use the DEFCON command (restrict server capabilities)
- "massmessage" # message all users on the server
opers:
supervisor:
class: "server-admin"
hidden: true
whois-line: I'm the supervisor, can I get a taxi number?
# `ergo genpasswd`.
password: "$2a$04$..."
logging:
-
method: stderr
type: "* -userinput -useroutput"
level: info
debug:
recover-from-errors: true
lock-file: "ircd.lock"
datastore:
path: ircd.db
autoupgrade: true
mysql:
enabled: false
languages:
enabled: true
default: en
path: languages
limits:
nicklen: 32
identlen: 20
channellen: 64
awaylen: 390
kicklen: 390
topiclen: 390
monitor-entries: 100
whowas-entries: 100
chan-list-modes: 60
registration-messages: 1024
multiline:
max-bytes: 4096 # 0 means disabled
max-lines: 100 # 0 means no limit
fakelag:
enabled: true
window: 1s
burst-limit: 5
messages-per-window: 2
cooldown: 5s
roleplay:
enabled: false
history:
enabled: true
channel-length: 2048
client-length: 256
autoresize-window: 2d
autoreplay-on-join: 20
chathistory-maxmessages: 1000
znc-maxmessages: 2048
restrictions:
expire-time: 3d
query-cutoff: 'join-time'
grace-period: 1h
persistent:
enabled: false
retention:
allow-individual-delete: false
enable-account-indexing: false
tagmsg-storage:
default: false
whitelist:
- "+draft/react"
- "+react"
allow-environment-overrides: false
Make sure to replace at least my-ircd
, xyz.onion
, and $2a$04$...
with
values that make sense for your setup.
Note: This is an example configuration that might work for your use case. It’s nevertheless a good idea to go through each individual setting and make sure it’s what you want.
ircd# cat /etc/ircd.motd
Put the content of your message-of-the-day file here.
Supervisord
To make sure our IRCd keeps running we’re going to install and configure
supervisord
:
ircd# pkg_add supervisor
ircd# cat /etc/supervisord.d/ircd.ini
[program:ircd]
command=ergo run --conf /etc/ircd.yaml
autostart=true
startsecs=5
startretries=5
autorestart=true
user=_ergo
environment=HOME="/home/_ergo",LC_ALL="en_US.UTF-8"
directory=/home/_ergo
ircd# rcctl enable supervisord
ircd# rcctl start supervisord
PF
Last but not least, add a basic firewall configuration:
ircd# cat /etc/pf.conf
##########
# Macros #
##########
minefield="1024:9999"
ssh_alternate_port=31231
##########
# Tables #
##########
table <bruteforce> persist
table <troublemakers> persist
###########
# Options #
###########
set skip on lo
#########
# Rules #
#########
block return # Block stateless traffic
pass # Establish keep-state
# Block brute-forcers
block quick proto tcp from <bruteforce> \
to (egress) port $ssh_alternate_port
block quick proto tcp from <troublemakers> \
to (egress) port $ssh_alternate_port
# By default, do not permit remote connections to X11
block return in on ! lo0 proto tcp to port 6000:6010
# Port build user does not need network
block return out log proto {tcp udp} user _pbuild
# Ergo user does not need network
block return out quick log proto {tcp udp} user _ergo
# Tor user needs network
pass out on egress proto tcp user _tor
# Connections that made it through to the SSH port but abusing it
pass proto tcp from any to (egress) \
port {$ssh_alternate_port} \
flags S/SA keep state \
(max-src-conn 5, max-src-conn-rate 5/5, \
overload <bruteforce> flush global \
)
# Port 22 is a dead-giveaway
# because we know we moved our SSH server
# elsewhere. Anyone poking there is up to no good.
pass in on egress proto tcp \
from any \
to (egress) port { \
telnet, \
ssh, \
netbios-ns, \
netbios-ssn, \
microsoft-ds \
} \
synproxy state \
tag trouble
# Tag stuff in the range $minefield as trouble ...
pass in on egress proto tcp from any to (egress) port $minefield \
synproxy state \
tag trouble
# ... unless it's to our alternate port
pass in proto tcp from any to (egress) port $ssh_alternate_port tag good
# Otherwise add to the troublemakers
pass proto tcp from any to (egress) port $ssh_alternate_port \
tagged trouble \
synproxy state \
(max-src-conn 1, max-src-conn-rate 1/10, \
overload <troublemakers> flush global \
)
We’re pretty much done with the basic setup!
Connecting to the IRCd
From a client computer you can now configure your favorite IRC client to connect to your new IRCd by …
- setting up a Tor client
- configuring your IRC client to use the Tor client’s SOCKS proxy for connections
- adding a new connection to your Onion address, on port 6667
I’m not going into these steps as these are different for every operating system and IRC client. What I usually do is that I set up ZNC and Tor on a dedicated VPS instance and use proxychains-ng to make ZNC connect to all its IRC networks via its local Tor SOCKS proxy. This allows me to connect from irssi on my workstation, via a clearnet VPN to ZNC, which in turn connects to multiple IRC networks via Tor Hidden Service connections. IRC networks like for example OFTC allow connection via their Onion addresses.
By using ZNC in between my local IRC client and IRC servers I’m not only increasing my own privacy on the networks but also avoid having to deal with proxy configurations for irssi or the disconnects/timeouts that come with IRC connections via Tor.
Enjoyed this? Support me via Monero, Bitcoin, Lightning, or Ethereum! More info.