From bf583b98d0ef05b8736ea02165a3a3c41f4d3b2f Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Wed, 14 Dec 2022 10:24:18 +0200 Subject: [PATCH] initial commit --- LICENSE | 12 +++ README.md | 221 ++++++++++++++++++++++++++++++++++++++++++ etc/dhcpd.conf | 8 ++ etc/dhcpd.interfaces | 1 + etc/hostname.em0 | 1 + etc/hostname.stge0 | 4 + etc/pf.conf | 41 ++++++++ etc/rc.d/dhcpd | 15 +++ etc/rc.d/sshd | 15 +++ etc/ssh/sshd_config | 93 ++++++++++++++++++ etc/sysctl.conf | 1 + firewall/fw-allow.sh | 40 ++++++++ firewall/fw-server.sh | 19 ++++ firewall/fw.sh | 29 ++++++ firewall/user/bob | 1 + firewall/user/jack | 2 + forward_opener.png | Bin 0 -> 14179 bytes 17 files changed, 503 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 etc/dhcpd.conf create mode 100644 etc/dhcpd.interfaces create mode 100644 etc/hostname.em0 create mode 100644 etc/hostname.stge0 create mode 100644 etc/pf.conf create mode 100755 etc/rc.d/dhcpd create mode 100755 etc/rc.d/sshd create mode 100644 etc/ssh/sshd_config create mode 100644 etc/sysctl.conf create mode 100644 firewall/fw-allow.sh create mode 100644 firewall/fw-server.sh create mode 100644 firewall/fw.sh create mode 100644 firewall/user/bob create mode 100644 firewall/user/jack create mode 100644 forward_opener.png diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e9d84ae --- /dev/null +++ b/LICENSE @@ -0,0 +1,12 @@ +Copyright (C) 2006 by Rob Landley + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3972a50 --- /dev/null +++ b/README.md @@ -0,0 +1,221 @@ +# Port Forward Opener on OpenBSD + +![Port Forward Opener](./forward_opener.png) + +This setup allow external users to connect to internal servers through port forwarding. + +Usefull for small offices when: + +- You cannot use vpn +- You don't like port knocking + +Let's say a user wants to connect to the server with RDP + +1. The **User** makes a SSH connection to firewall. +2. The Firewall allows for a few seconds the IP of the user to connect to the internal server. +3. The **User** makes a RDP connection to the internal server +3. The Firewall closes SSH connection automatically. + +**It's not ready for production, don't use it if you don't know exactly what you are doing** + +## Addressing Scheme Example + +``` + + [Desktop A] [Desktop B] [Desktop C] + | | | +[DSL]---[Firewall]---[Switch]---+-----+-----+-----+----+ + | | + [Server] [Printer] + + + + Address Node Port Forward + ------------- ------------ ------------------------ + 192.168.1.1 DSL Router + 192.168.1.2 Firewall WAN 22 < 60022 + 192.168.2.1 Firewall LAN + + 192.168.2.11 Desktop A 22 < 60122, 3389 < 63381 + 192.168.2.12 Desktop B 22 < 60222, 3389 < 63382 + 192.168.2.13 Desktop C 22 < 60322, 3389 < 63383 + + 192.168.2.200 Server 3389 < 63389 + 192.168.2.201 Printer + +``` + +## Dependencies + +```bash +pkg_add vim dialog # nothing works properly without vim +``` + +## Network Settings + + +:floppy_disk: `vi /etc/hostname.em0` + +```bash +dhcp +``` + +:floppy_disk: `vi /etc/hostname.stge0` + +```bash +media 100baseTX +mediaopt full-duplex +inet 192.168.2.1 0xffffff00 +``` + +:floppy_disk: `vi /etc/mygate` + +```bash +192.168.1.1 +``` + +:floppy_disk: `vi /etc/resolv.conf` + +```bash +nameserver 9.9.9.9 +nameserver 1.1.1.1 +``` + +## DHCP Server + +:floppy_disk: `vi /etc/dhcpd.conf` + +```bash +option domain-name "taxstudio"; +option domain-name-servers 9.9.9.9; + +subnet 192.168.2.0 netmask 255.255.255.0 { + option routers 192.168.2.1; + range 192.168.2.11 192.168.2.19; +} +``` + +:floppy_disk: `vi /etc/dhcpd.interfaces` + +```bash +stge0 +``` + +Test server `dhcpd -d -c /etc/dhcpd.conf` + +Start DHCP server at boot + +```bash +rcctl set dhcpd flags stge0 +rcctl enable dhcpd +rcctl start dhcpd +``` + +Show leases + +```bash +cat /var/db/dhcpd.lease +``` + + +## Firewall + +Port forwarding + +```bash +sysctl net.inet.ip.forwarding=1 +echo 'net.inet.ip.forwarding=1' >> /etc/sysctl.conf +sysctl | grep ip.forwarding +``` + +Rules + +:floppy_disk: `vi /etc/pf.conf` + +```bash +wan = em0 +lan = stge0 + +#---------------------------------- +# Defaults +#---------------------------------- +pass out keep state + +set skip on lo +set block-policy return +set reassemble yes + +block in all +block return + +match in on $wan scrub (no-df max-mss 1440) +match out on $wan scrub (random-id) + +antispoof quick for { $wan lo0 } + +#---------------------------------- +# Input +#---------------------------------- +pass quick on $lan +pass in quick proto tcp to $wan port 22 keep state +pass in quick on $lan + +#---------------------------------- +# Output +#---------------------------------- +pass out on $lan inet keep state + +#---------------------------------- +# NAT +#---------------------------------- +match out on $wan from !($wan) nat-to ($wan) + +# Allow outgoing traffic for LAN and the gateway +pass out quick keep state +pass in on { $lan } inet +``` + +Enable + +```bash +pfctl -d +pfctl -f /etc/pf.conf +pfctl -e +``` + + +## Setup Opener + +Start at boot and listens the port 3000 + +```bash +echo "/firewall/fw-server.sh" >> /etc/rc.local +``` + +Add Users + +1. Add user to system + +```bash +# fw.sh will executed on user login +useradd -d /dev/null -s /firewall/fw.sh jack +passwd jack +``` + +2. Add user to opener + +Add one or more pf rules to user file. + +```bash +vi firewall/user/jack +``` +An example to allow user jack to make RDP connections to host 192.168.2.12: + +`pass in proto tcp from IP to $wan port 63389 rdr-to 192.168.2.12 port 3388` + +The `$wan` changes according to the address of the SSH connection that the user makes every time + +## Todo + +- chroot shell +- email activity diff --git a/etc/dhcpd.conf b/etc/dhcpd.conf new file mode 100644 index 0000000..e29e1dd --- /dev/null +++ b/etc/dhcpd.conf @@ -0,0 +1,8 @@ +option domain-name "taxstudio.gr"; +option domain-name-servers 9.9.9.9; + +subnet 192.168.2.0 netmask 255.255.255.0 { + option routers 192.168.2.1; + range 192.168.2.11 192.168.2.19; +} + diff --git a/etc/dhcpd.interfaces b/etc/dhcpd.interfaces new file mode 100644 index 0000000..3dcb18f --- /dev/null +++ b/etc/dhcpd.interfaces @@ -0,0 +1 @@ +stge0 diff --git a/etc/hostname.em0 b/etc/hostname.em0 new file mode 100644 index 0000000..72ab18f --- /dev/null +++ b/etc/hostname.em0 @@ -0,0 +1 @@ +dhcp diff --git a/etc/hostname.stge0 b/etc/hostname.stge0 new file mode 100644 index 0000000..a28195f --- /dev/null +++ b/etc/hostname.stge0 @@ -0,0 +1,4 @@ +media 100baseTX +mediaopt full-duplex +inet 192.168.2.1 0xffffff00 + diff --git a/etc/pf.conf b/etc/pf.conf new file mode 100644 index 0000000..aedb3e2 --- /dev/null +++ b/etc/pf.conf @@ -0,0 +1,41 @@ +wan = em0 +lan = stge0 + +#---------------------------------- +# Defaults +#---------------------------------- +pass out keep state + +set skip on lo +set block-policy return +set reassemble yes + +block in all +block return + +match in on $wan scrub (no-df max-mss 1440) +match out on $wan scrub (random-id) + +antispoof quick for { $wan lo0 } + +#---------------------------------- +# Input +#---------------------------------- +pass quick on $lan +pass in quick proto tcp to $wan port 22 keep state +pass in quick on $lan + +#---------------------------------- +# Output +#---------------------------------- +pass out on $lan inet keep state + +#---------------------------------- +# NAT +#---------------------------------- +match out on $wan from !($wan) nat-to ($wan) + +# Allow outgoing traffic for LAN and the gateway +pass out quick keep state +pass in on { $lan } inet + diff --git a/etc/rc.d/dhcpd b/etc/rc.d/dhcpd new file mode 100755 index 0000000..30a4dcd --- /dev/null +++ b/etc/rc.d/dhcpd @@ -0,0 +1,15 @@ +#!/bin/ksh +# +# $OpenBSD: dhcpd,v 1.3 2018/01/11 19:52:12 rpe Exp $ + +daemon="/usr/sbin/dhcpd" + +. /etc/rc.d/rc.subr + +rc_reload=NO + +rc_pre() { + touch /var/db/dhcpd.leases +} + +rc_cmd $1 diff --git a/etc/rc.d/sshd b/etc/rc.d/sshd new file mode 100755 index 0000000..94cc554 --- /dev/null +++ b/etc/rc.d/sshd @@ -0,0 +1,15 @@ +#!/bin/ksh +# +# $OpenBSD: sshd,v 1.6 2020/01/25 12:05:08 sthen Exp $ + +daemon="/usr/sbin/sshd" + +. /etc/rc.d/rc.subr + +pexp="sshd: ${daemon}${daemon_flags:+ ${daemon_flags}} \[listener\].*" + +rc_reload() { + ${daemon} ${daemon_flags} -t && pkill -HUP -xf "${pexp}" +} + +rc_cmd $1 diff --git a/etc/ssh/sshd_config b/etc/ssh/sshd_config new file mode 100644 index 0000000..a2cc3c5 --- /dev/null +++ b/etc/ssh/sshd_config @@ -0,0 +1,93 @@ +# $OpenBSD: sshd_config,v 1.103 2018/04/09 20:41:22 tj Exp $ + +# This is the sshd server system-wide configuration file. See +# sshd_config(5) for more information. + +# The strategy used for options in the default sshd_config shipped with +# OpenSSH is to specify options with their default value where +# possible, but leave them commented. Uncommented options override the +# default value. + +#Port 22 +#AddressFamily any +#ListenAddress 0.0.0.0 +#ListenAddress :: + +#HostKey /etc/ssh/ssh_host_rsa_key +#HostKey /etc/ssh/ssh_host_ecdsa_key +#HostKey /etc/ssh/ssh_host_ed25519_key + +# Ciphers and keying +#RekeyLimit default none + +# Logging +#SyslogFacility AUTH +#LogLevel INFO + +# Authentication: + +#LoginGraceTime 2m +PermitRootLogin yes +#StrictModes yes +#MaxAuthTries 6 +#MaxSessions 10 + +#PubkeyAuthentication yes + +# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2 +# but this is overridden so installations will only check .ssh/authorized_keys +AuthorizedKeysFile .ssh/authorized_keys + +#AuthorizedPrincipalsFile none + +#AuthorizedKeysCommand none +#AuthorizedKeysCommandUser nobody + +# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts +#HostbasedAuthentication no +# Change to yes if you don't trust ~/.ssh/known_hosts for +# HostbasedAuthentication +#IgnoreUserKnownHosts no +# Don't read the user's ~/.rhosts and ~/.shosts files +#IgnoreRhosts yes + +# To disable tunneled clear text passwords, change to no here! +#PasswordAuthentication yes +#PermitEmptyPasswords no + +# Change to no to disable s/key passwords +#ChallengeResponseAuthentication yes + +#AllowAgentForwarding yes +#AllowTcpForwarding yes +#GatewayPorts no +#X11Forwarding no +#X11DisplayOffset 10 +#X11UseLocalhost yes +#PermitTTY yes +#PrintMotd yes +#PrintLastLog yes +#TCPKeepAlive yes +#PermitUserEnvironment no +#Compression delayed +#ClientAliveInterval 0 +#ClientAliveCountMax 3 +#UseDNS no +#PidFile /var/run/sshd.pid +#MaxStartups 10:30:100 +#PermitTunnel no +#ChrootDirectory none +#VersionAddendum none + +# no default banner path +#Banner none + +# override default of no subsystems +Subsystem sftp /usr/libexec/sftp-server + +# Example of overriding settings on a per-user basis +#Match User anoncvs +# X11Forwarding no +# AllowTcpForwarding no +# PermitTTY no +# ForceCommand cvs server diff --git a/etc/sysctl.conf b/etc/sysctl.conf new file mode 100644 index 0000000..cd3cf17 --- /dev/null +++ b/etc/sysctl.conf @@ -0,0 +1 @@ +net.inet.ip.forwarding=1 diff --git a/firewall/fw-allow.sh b/firewall/fw-allow.sh new file mode 100644 index 0000000..f5a7586 --- /dev/null +++ b/firewall/fw-allow.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env ksh + +# +# Forward Opener (server helper) +# +# for OpenBSD +# by ste.vaidis@gmail.com +# + +if [ $# -ne 2 ]; then + logger -t "FORWARD" "fw-allow.sh executed without proper arguments: user:$1 ip:$2" + exit +fi + +USER=$1 +IP=$2 + +logger -t "FORWARD" "Open for user $USER from $IP" + +cat /firewall/user/$USER | sed "s/IP/$IP/g" > /firewall/user/$USER.tmp +echo "include '/firewall/user/$USER.tmp'" >> /etc/pf.conf +if [[ $? != 0 ]]; then echo "Fail"; fi + +pfctl -f /etc/pf.conf +if [[ $? != 0 ]]; then echo "Fail"; fi + +sleep 15 +if [[ $? != 0 ]]; then echo "Fail"; fi + +sed -i "/$USER/d" /etc/pf.conf +if [[ $? != 0 ]]; then echo "Fail"; fi + +pfctl -f /etc/pf.conf +if [[ $? != 0 ]]; then echo "Fail"; fi + +logger -t "FORWARD" "Close for user $USER from $IP" + +rm /firewall/user/$USER.tmp + +exit diff --git a/firewall/fw-server.sh b/firewall/fw-server.sh new file mode 100644 index 0000000..4f19248 --- /dev/null +++ b/firewall/fw-server.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env ksh + +# +# Forward Opener (server) +# +# for OpenBSD +# by ste.vaidis@gmail.com +# + +/usr/local/bin/ncat -lk localhost 3000 | ( + while read c; do + USER=$(echo $c | awk {'print $1'}) + IP=$(echo $c | awk {'print $2'}) + ps x | grep fw-allow.sh | grep $USER | grep -v grep + if [ $? -eq 1 ]; then + /firewall/fw-allow.sh $USER $IP + fi + done +) diff --git a/firewall/fw.sh b/firewall/fw.sh new file mode 100644 index 0000000..0ef4907 --- /dev/null +++ b/firewall/fw.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env ksh + +# +# Let-Me-In (client) +# +# for OpenBSD +# by ste.vaidis@gmail.com +# + +TITLE="OpenBSD Firewall" +USER=$(whoami) +IP=$(w | grep $USER | awk {'print $3'}) + +echo "$USER $IP" | nc -w1 localhost 3000 & + +( + items=15 + processed=0 + while [ $processed -le $items ]; do + pct=$(( $processed * 100 / $items )) + echo "XXX\n" + echo "\nHello $USER from $IP" + echo "\nYou have 15 seconds to connect ($processed)" + echo "XXX" + echo "$pct" + processed=$((processed+1)) + sleep 1 + done +) | dialog --title "$TITLE" --gauge "\nWait please..." 10 50 0 \ No newline at end of file diff --git a/firewall/user/bob b/firewall/user/bob new file mode 100644 index 0000000..6f7f026 --- /dev/null +++ b/firewall/user/bob @@ -0,0 +1 @@ +pass in log proto tcp to $wan port 60122 rdr-to 192.168.2.11 port 22 diff --git a/firewall/user/jack b/firewall/user/jack new file mode 100644 index 0000000..d6e4706 --- /dev/null +++ b/firewall/user/jack @@ -0,0 +1,2 @@ +pass in proto tcp from IP to $wan port 63389 rdr-to 192.168.2.222 port 3388 +pass in proto tcp from IP to $wan port 60322 rdr-to 192.168.2.222 port 22 \ No newline at end of file diff --git a/forward_opener.png b/forward_opener.png new file mode 100644 index 0000000000000000000000000000000000000000..e744d2ca1374f327b72ba30d96a487315c9843d4 GIT binary patch literal 14179 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 ahk))JLiSiL%vW+)YD!u!D_*=0{=Wd^{>r@o literal 0 HcmV?d00001