Skip to content

Commit

Permalink
optional all-traffic routing, fixed a number of authentication bugs.
Browse files Browse the repository at this point in the history
  • Loading branch information
jonnyhyman committed May 21, 2021
1 parent 12d39d3 commit e9316c5
Show file tree
Hide file tree
Showing 16 changed files with 525 additions and 93 deletions.
Binary file modified .DS_Store
Binary file not shown.
Binary file added images/.DS_Store
Binary file not shown.
Binary file modified rmc/.DS_Store
Binary file not shown.
2 changes: 2 additions & 0 deletions rmc/auth/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ async def tcp_authenticate_request(QUEUE, S_IP, S_PORT, SPASS, encrypted):
auth_reply = await reader.read(1024)

try:

auth_reply = Fernet(str(SPASS,'utf-8')).decrypt(auth_reply)
auth_reply = auth_reply.decode()
QUEUE.put(f"Server Authentication Reply: {auth_reply}")
print(f"Server Authentication Reply: {auth_reply}")

auth_reply = auth_reply.split(',')
QUEUE.put(auth_reply)
Expand Down
27 changes: 14 additions & 13 deletions rmc/auth/server.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
from auth.crypt import passkey, Fernet, InvalidToken
import asyncio

from datetime import datetime

async def handle_authentication(reader, writer, QUEUE,
userlist, server_pass, wg_port):
userlist, server_pass, wg_port, wg_only, subnet):

auth_request = await reader.read(1024)
fail_cause = "UNKNOWN ERROR"

for user in userlist: # skip server
# loop through usernames, see if one properly decrpyts message
# hash key to decrypt message with:
key = str(passkey(user['name']), 'utf-8')

auth_key = passkey(user['name'] + str(datetime.utcnow().minute))

try:

addr = writer.get_extra_info('peername')
# QUEUE.put(f">>> Received {message} from {addr}")

# decrypt message
message = Fernet(key).decrypt(auth_request)
message = Fernet(auth_key).decrypt(auth_request)
message = message.decode()

PASS_CHECK, PKEYU = message.split(',')
Expand All @@ -33,9 +37,10 @@ async def handle_authentication(reader, writer, QUEUE,
PKEYS = userlist[0]['Pk']
SERVER_IP = userlist[0]['ip']
ASSIGN_IP = user['ip']
WG_PORT = wg_port

auth_reply = f"{PKEYS},{SERVER_IP},{ASSIGN_IP},{WG_PORT}"
auth_reply = (f"{PKEYS},{SERVER_IP},{ASSIGN_IP},"
f"{wg_port},{wg_only},{subnet}")

auth_reply = Fernet(server_pass).encrypt(auth_reply.encode())
writer.write(auth_reply)
await writer.drain()
Expand All @@ -55,7 +60,7 @@ async def handle_authentication(reader, writer, QUEUE,

writer.close()

def tcp_server(QUEUE, S_IP, S_PORT, userlist, server_pass, wg_port):
def tcp_server(S_IP, S_PORT, QUEUE, DETAILS):
""" QUEUE: multiprocessing.Queue for output and updates
when latest QUEUE item type is list, the request is complete!
"""
Expand All @@ -64,20 +69,16 @@ def tcp_server(QUEUE, S_IP, S_PORT, userlist, server_pass, wg_port):
authentication = loop.create_future()
asyncio.set_event_loop(loop)

handle_authentication_ = lambda r,w: handle_authentication(r,w,
QUEUE,
userlist,
server_pass,
wg_port
)
handle_authentication_ = lambda r,w: handle_authentication(r,w,QUEUE,*DETAILS)

coro = asyncio.start_server(handle_authentication_, S_IP, S_PORT, loop=loop)

try:
async_server = loop.run_until_complete(coro)
except OSError as e:
# The port is already in use
QUEUE.put(e)
print(e)
QUEUE.put(str(e))
return

# Serve requests until Ctrl+C is pressed
Expand Down
38 changes: 25 additions & 13 deletions rmc/rmc_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
from pathlib import Path
import sys

import datetime
from multiprocessing import Process, Queue
import multiprocessing
import platform

from datetime import datetime
import time

import validators
import webbrowser
import os
Expand Down Expand Up @@ -77,12 +79,8 @@ def __init__(self, app, parent=None):
if self.config['auth']['client_username'] != "":
self.update_header()

update_function = lambda: self.users.update(self.config['userlist'])

update_function()
# self.user_timer = QTimer(self)
# self.user_timer.timeout.connect(update_function)
# self.user_timer.start(60_000)
self.update_userview = lambda: self.users.update(self.config['userlist'])
self.update_userview()

def update_header(self):
header = f"""## {self.config['auth']['client_username']}\n"""
Expand Down Expand Up @@ -145,7 +143,14 @@ def auth_client(self):
if auth_tcp.tcp_authentic:

# Create Wireguard tunnel config file
PKEYS, S_IP, IP_ASSIGNED, WG_PORT = auth_tcp.tcp_authentic
PKEYS, S_IP, IP_ASSIGNED, WG_PORT, WG_ONLY, SUBNET = auth_tcp.tcp_authentic

if WG_ONLY == "True":
allowed_ips = SUBNET
else:
allowed_ips = "0.0.0.0/0, ::/0" # all ipv4, all ipv6

# TODO: Add subnet to tcp autentic and

client_config = (f"""
[Interface]
Expand All @@ -165,7 +170,7 @@ def auth_client(self):
# port here to match.
Endpoint = {auth_request['S_IP']}:{WG_PORT}
# Informs Wireguard to forward ALL traffic through the VPN.
AllowedIPs = 0.0.0.0/0, ::/0
AllowedIPs = {allowed_ips}
# If you're be behind a NAT, this will keep the connection alive.
PersistentKeepalive = 25
""")
Expand All @@ -174,8 +179,8 @@ def auth_client(self):
self.config['userlist'][0]['Pk'] = PKEYS
self.config.save()

# new in 0.1.1
self.update_header()
self.update_userview()

# Save config to conf file
saveto = FileDialog(forOpen=False, fmt='conf',
Expand Down Expand Up @@ -413,7 +418,14 @@ def __init__(self, parent = None):
MESSG = f"{SPASS.decode()},{Pk}"

# Authentication request
encrypted = Fernet(passkey(UNAME)).encrypt(MESSG.encode())
# username + utc minute (time based salt)

# wait for next minute if we're literally in the last 1 second
if datetime.utcnow().second == 59:
time.sleep(1)

auth_key = passkey(UNAME + str(datetime.utcnow().minute))
encrypted = Fernet(auth_key).encrypt(MESSG.encode())

self.tcp_queue = Queue()
self.tcp_proc = Process(target = tcp_client,
Expand All @@ -440,7 +452,7 @@ def update_status(self):

elif type(update) == list:
# This marks the end of the request, a list of
# [PKEYS, SERVER_IP, IP_ASSIGNED, WG_PORT]
# [PKEY SERVER, SERVER_IP, etc...]
self.tcp_authentic = update

def closeEvent(self, event):
Expand All @@ -455,7 +467,7 @@ def closeEvent(self, event):
app = QApplication(sys.argv)
app.setApplicationName("Resolve Mission Control Client")

icon = QIcon(link('ui/icons/icon.ico'))
icon = QIcon(link('ui/icons/icon_v1.ico'))
app.setWindowIcon(icon)

w = Client(app)
Expand Down
2 changes: 1 addition & 1 deletion rmc/rmc_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
from pathlib import Path


__version__ = "0.1.1"
__version__ = "0.1.2"


def link(relpath):
Expand Down
77 changes: 67 additions & 10 deletions rmc/rmc_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class Server(UI_Common):
subnet = '9.0.0.0/24'
auth_port = 4444
wg_port = 51820
wg_only = True

def __init__(self, app, parent=None):
super().__init__(app, parent=parent)
Expand Down Expand Up @@ -121,6 +122,7 @@ def __init__(self, app, parent=None):
self.b_auth.setEnabled(True)
self.b_tunn.setEnabled(True)
self.wg_port = self.config['wireguard']['port']
self.wg_only = self.config['wireguard']['only']
self.setup_window.step_enable(['Port Forward',
'Authenticate Remote User'])

Expand Down Expand Up @@ -344,6 +346,10 @@ def new_user(self, user=None):

self.update_userview()

# Restart authentication server (send new userlist)
self.toggle_auth(False)
self.toggle_auth(True)

def toggle_auth(self, state):
""" Toggle authentication server """
if state:
Expand All @@ -366,17 +372,21 @@ def open_authentication(self):

self.tcp_queue = Queue()
self.tcp_proc = Process(target = tcp_server,
args = (self.tcp_queue, C_IP, S_PORT,
self.config['userlist'],
self.config['auth']['authkey'],
self.wg_port,))
args = (C_IP, S_PORT, self.tcp_queue,
[self.config['userlist'],
self.config['auth']['authkey'],
self.wg_port,
self.wg_only,
self.subnet],
)
)

self.tcp_timer = QTimer(self)
self.tcp_timer.timeout.connect(self.update_authentication)
self.tcp_timer.start(500)
self.tcp_proc.start()

self.message.setText("Authentication server opened")
self.message.setText("Authentication server opening...")

def update_authentication(self):
""" Check for updates on the authentication server """
Expand All @@ -397,6 +407,7 @@ def update_authentication(self):

def close_authentication(self):
""" Close the authentication server """

if hasattr(self, 'tcp_proc'):
# Running? Stop.
self.tcp_timer.stop()
Expand All @@ -406,7 +417,9 @@ def close_authentication(self):

self.message.setText("Authentication server closed")

# Update HBA here in case if failed somewhere else
del self.tcp_proc

# Update HBA here in case it failed somewhere else
self.update_hba()

def authenticated_user(self, new_user):
Expand Down Expand Up @@ -454,6 +467,10 @@ def remove_user(self, username):

self.update_userview()

# Restart authentication server (send new userlist)
self.toggle_auth(False)
self.toggle_auth(True)

def database_create(self):
""" Create a new PostgreSQL database """

Expand Down Expand Up @@ -727,22 +744,26 @@ def config_tunnel(self):

prompt = TunnelConfig(self)
prompt_result = prompt.exec_()
PORT = prompt.get_output()
PORT, ONLY = prompt.get_output()

if not prompt_result:
return

if PORT == "":
PORT = self.wg_port

# Port verify

try:
self.wg_port = int(PORT)

except ValueError as e:
UI_Error(self, "Invalid Assignment Port", "Was port as an integer?")
UI_Error(self, "Invalid Assignment Port", "Was port an integer?")
self.config_tunnel()
return

self.wg_only = ONLY

if platform.system().lower() == 'darwin':

try:
Expand Down Expand Up @@ -774,6 +795,7 @@ def config_tunnel(self):

self.config['wireguard'] = {}
self.config['wireguard']['port'] = self.wg_port
self.config['wireguard']['only'] = self.wg_only
self.config['wireguard']['pk'] = self.wireguard.pk
self.config['wireguard']['Pk'] = self.wireguard.Pk
self.config['userlist'][0]['Pk'] = self.wireguard.Pk
Expand All @@ -785,6 +807,11 @@ def config_tunnel(self):
self.b_tunn.setEnabled(True)
self.b_auth.setEnabled(True)

self.setup_window.step_enable(['Port Forward',
'Authenticate Remote User'])

self.update_userview()

def toggle_tunnel(self, state):
""" Toggle the Wireguard directly with wg or wg-quick up/down
"""
Expand Down Expand Up @@ -1065,6 +1092,29 @@ def __init__(self, parent = None):
layout.addWidget(info)
layout.addWidget(self.WG_PORT)

self.WG_ALL = QtWidgets.QPushButton("All traffic")
self.WG_ALL.setCheckable(True)
self.WG_ALL.setChecked(False)

self.WG_ONLY = QtWidgets.QPushButton("Resolve traffic only")
self.WG_ONLY.setCheckable(True)
self.WG_ONLY.setChecked(True)

self.WG_ALL.clicked.connect(lambda x: self.WG_ONLY.setChecked(not x))
self.WG_ONLY.clicked.connect(lambda x: self.WG_ALL.setChecked(not x))

pathbox = QHBoxLayout()
pathbox.addWidget(self.WG_ALL)
pathbox.addWidget(self.WG_ONLY)
pathbox.setContentsMargins(0,0,0,0)
pathbox.setSpacing(0)

info = QLabel("Route _all_ user traffic through Wireguard, or _only_ __Resolve__ traffic?")
info.setTextFormat(Qt.MarkdownText)

layout.addWidget(info)
layout.addLayout(pathbox)

# OK and Cancel buttons
buttons = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel,
Expand All @@ -1075,7 +1125,14 @@ def __init__(self, parent = None):
layout.addWidget(buttons)

def get_output(self):
return self.WG_PORT.text()

if self.WG_ALL.isChecked():
WG_ONLY = False

if self.WG_ONLY.isChecked():
WG_ONLY = True

return self.WG_PORT.text(), WG_ONLY


class DatabaseConfig(UI_Dialog):
Expand Down Expand Up @@ -1129,7 +1186,7 @@ def get_output(self):
app = QApplication(sys.argv)
app.setApplicationName("Resolve Mission Control Server")

icon = QIcon(link('ui/icons/icon.ico'))
icon = QIcon(link('ui/icons/icon_v1.ico'))
app.setWindowIcon(icon)

w = Server(app)
Expand Down
Loading

0 comments on commit e9316c5

Please sign in to comment.