Skip to content

Commit

Permalink
[Security] enable SSL for all servers; add local auth for websockify …
Browse files Browse the repository at this point in the history
…server; clean up websockify server after disconnection
  • Loading branch information
junhaoliao committed Jun 10, 2022
1 parent 389f5c2 commit ccdfdc9
Show file tree
Hide file tree
Showing 10 changed files with 76 additions and 33 deletions.
10 changes: 6 additions & 4 deletions application/features/Audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
from typing import Optional

import paramiko
from SimpleWebSocketServer import SimpleSSLWebSocketServer, WebSocket, SimpleWebSocketServer
from SimpleWebSocketServer import SimpleSSLWebSocketServer, WebSocket
from werkzeug.serving import generate_adhoc_ssl_context

from .Connection import Connection
from .. import app
Expand All @@ -37,6 +38,7 @@
TRY_FFMPEG_MAX_COUNT = 3
AUDIO_BUFFER_SIZE = 20480


class Audio(Connection):
def __init__(self):
self.id = None
Expand Down Expand Up @@ -167,9 +169,9 @@ def handleClose(self):
print("AUDIO_PORT =", AUDIO_PORT)

if os.environ.get('SSL_CERT_PATH') is None:
# no certificate provided, run in non-encrypted mode
# FIXME: consider using a self-signing certificate for local connections
audio_server = SimpleWebSocketServer('', AUDIO_PORT, AudioWebSocket)
# no certificate provided, generate self-signing certificate
audio_server = SimpleSSLWebSocketServer('', AUDIO_PORT, AudioWebSocket,
ssl_context=generate_adhoc_ssl_context())
else:
import ssl

Expand Down
9 changes: 5 additions & 4 deletions application/features/Term.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
import uuid
from typing import Optional

from SimpleWebSocketServer import SimpleSSLWebSocketServer, WebSocket, SimpleWebSocketServer
from SimpleWebSocketServer import SimpleSSLWebSocketServer, WebSocket
from paramiko import Channel
from werkzeug.serving import generate_adhoc_ssl_context

from .Connection import Connection
from .. import app
Expand Down Expand Up @@ -119,9 +120,9 @@ def handleClose(self):
print("TERMINAL_PORT =", TERMINAL_PORT)

if os.environ.get('SSL_CERT_PATH') is None:
# no certificate provided, run in non-encrypted mode
# FIXME: consider using a self-signing certificate for local connections
terminal_server = SimpleWebSocketServer('', TERMINAL_PORT, TermWebSocket)
# no certificate provided, generate self-signing certificate
terminal_server = SimpleSSLWebSocketServer('', TERMINAL_PORT, TermWebSocket,
ssl_context=generate_adhoc_ssl_context())
else:
import ssl

Expand Down
18 changes: 10 additions & 8 deletions application/features/VNC.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,23 @@
import re
import threading

import websockify

from .Connection import Connection
from .mywebsockify import MyProxyRequestHandler, MySSLProxyServer
from .vncpasswd import decrypt_passwd, obfuscate_password
from ..utils import find_free_port


def websocket_proxy_thread(local_websocket_port, local_vnc_port):
if os.environ.get('SSL_CERT_PATH') is None:
# no certificate provided, run in non-encrypted mode
# FIXME: consider using a self-signing certificate for local connections
proxy_server = websockify.LibProxyServer(listen_port=local_websocket_port, target_host='',
target_port=local_vnc_port,
run_once=True)
proxy_server.serve_forever()
proxy_server = MySSLProxyServer(RequestHandlerClass=MyProxyRequestHandler,
listen_port=local_websocket_port, target_host='',
target_port=local_vnc_port)

# only serve two request:
# 1st: first handshake: upgrade the HTTP request
# 2nd: actually serve the ws connection
for _ in range(2):
proxy_server.handle_request()
proxy_server.server_close()
else:
import subprocess
Expand Down
23 changes: 23 additions & 0 deletions application/features/mywebsockify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import websockify
from werkzeug.serving import generate_adhoc_ssl_context

from application.utils import local_auth


class MyProxyRequestHandler(websockify.ProxyRequestHandler):
def auth_connection(self):
super(MyProxyRequestHandler, self).auth_connection()
if not local_auth(headers=self.headers, abort_func=self.server.server_close):
# local auth failure
return


class MySSLProxyServer(websockify.LibProxyServer):
# noinspection PyPep8Naming
def __init__(self, RequestHandlerClass=websockify.ProxyRequestHandler, ssl_context=None, **kwargs):
super(MySSLProxyServer, self).__init__(RequestHandlerClass=RequestHandlerClass, **kwargs)

if ssl_context is None:
# no certificate provided, generate self-signing certificate
ssl_context = generate_adhoc_ssl_context()
self.socket = ssl_context.wrap_socket(self.socket, server_side=True)
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@
"last 1 safari version"
]
},
"proxy": "http://localhost:5000",
"proxy": "https://localhost:5000",
"//": "https://ictrl.ca"
}
4 changes: 2 additions & 2 deletions client/src/actions/audio.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ export const launch_audio = (vncsd, sessionID) => {
}).then((response) => {
const {port, audio_id} = response.data;
const socket = new WebSocket(
`${process.env.REACT_APP_DOMAIN_NAME ? 'wss' : 'ws'}
://${process.env.REACT_APP_DOMAIN_NAME || '127.0.0.1'}:${port}/${audio_id}`);
`wss://${process.env.REACT_APP_DOMAIN_NAME ||
'127.0.0.1'}:${port}/${audio_id}`);
socket.binaryType = 'arraybuffer';

socket.onopen = (_) => {
Expand Down
4 changes: 2 additions & 2 deletions client/src/actions/term.js
Original file line number Diff line number Diff line change
Expand Up @@ -611,8 +611,8 @@ const setupWebGL = (term) => {
const setupWebSocket = (term, term_id, port) => {

const socket = new WebSocket(
`${process.env.REACT_APP_DOMAIN_NAME ? 'wss' : 'ws'}
://${process.env.REACT_APP_DOMAIN_NAME || '127.0.0.1'}:${port}/${term_id}`);
`wss://${process.env.REACT_APP_DOMAIN_NAME ||
'127.0.0.1'}:${port}/${term_id}`);

socket.onopen = (_) => {
const attachAddon = new AttachAddon(socket);
Expand Down
5 changes: 2 additions & 3 deletions client/src/actions/vnc.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ import {htmlResponseToReason, isIOS} from './utils';

const setupDOM = (port, passwd) => {
/* Creating a new RFB object and start a new connection */
const url = `${process.env.REACT_APP_DOMAIN_NAME ? 'wss' : 'ws'}
://${process.env.REACT_APP_DOMAIN_NAME || '127.0.0.1'}:${port}`;
const url = `wss://${process.env.REACT_APP_DOMAIN_NAME ||
'127.0.0.1'}:${port}`;
const rfb = passwd ?
new RFB(
document.getElementById('screen'),
Expand Down Expand Up @@ -257,7 +257,6 @@ export const vncConnect = async (vncViewer) => {
// hide the Loading element
vncViewer.setState({
loading: false,
disconnected: false, // sometimes 'disconnect' (below) is fired before 'connect' for unknown reasons
});
});

Expand Down
32 changes: 24 additions & 8 deletions desktop_client/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@ if (handleSquirrelEvent()) {
}

// Modules to control application life and create native browser window
const {app, BrowserWindow, Menu, MenuItem, ipcMain, session} = require(
'electron');
const {randomUUID} = require('crypto');
const {spawn} = require('child_process');
const {resolve} = require('path');
const {app} = require('electron');

const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
Expand All @@ -23,8 +19,13 @@ if (!gotTheLock) {
process.exit();
}

const {BrowserWindow, Menu, MenuItem, ipcMain, session} = require('electron');
const {randomUUID} = require('crypto');
const {spawn} = require('child_process');
const {resolve} = require('path');
const {getFreePort, humanFileSize} = require('./utils');
const ProgressBar = require('./ProgressBar');

const mainPort = getFreePort();
const localAuthKey = randomUUID();

Expand Down Expand Up @@ -65,7 +66,7 @@ if (isMac) {

const setupNewWindowIcon = (url, newWindow) => {
const {nativeImage} = require('electron');
const {get} = require('http');
const {get} = require('https');

const url_split = url.split('/');
const sessionId = url_split[url_split.length - 1];
Expand All @@ -75,6 +76,7 @@ const setupNewWindowIcon = (url, newWindow) => {
port: mainPort,
path: `/api/favicon/${feature}/${sessionId}`,
headers: {'Authorization': `Bearer ${localAuthKey}`},
rejectUnauthorized: false,
}, (msg) => {
const result = [];

Expand All @@ -86,6 +88,8 @@ const setupNewWindowIcon = (url, newWindow) => {
const icon = nativeImage.createFromBuffer(Buffer.concat(result));
newWindow.setIcon(icon);
});
}).on('error', (e) => {
console.error(e);
});
};

Expand Down Expand Up @@ -120,14 +124,26 @@ let mainWindow = null;
const setupLocalAuth = () => {
// Modify the user agent for all requests to the following urls.
const filter = {
urls: ['http://127.0.0.1/*', 'ws://127.0.0.1/*'],
urls: ['https://127.0.0.1/*', 'wss://127.0.0.1/*'],
};

session.defaultSession.webRequest.onBeforeSendHeaders(filter,
(details, callback) => {
details.requestHeaders['Authorization'] = `Bearer ${localAuthKey}`;
callback({requestHeaders: details.requestHeaders});
});

app.on('certificate-error',
(event, webContents, url, error, certificate, callback) => {
if (url.startsWith('https://127.0.0.1') ||
url.startsWith('wss://127.0.0.1')) {
event.preventDefault();
callback(true);
} else {
console.error('Certificate Error at', url);
app.quit();
}
});
};

const createDashboardWindow = () => {
Expand All @@ -137,7 +153,7 @@ const createDashboardWindow = () => {

// load dashboard
mainWindow.setTitle('Loading... ');
mainWindow.loadURL(`http://127.0.0.1:${mainPort}/dashboard`);
mainWindow.loadURL(`https://127.0.0.1:${mainPort}/dashboard`);
// need to reload on Mac because the first load times out very quickly
mainWindow.webContents.on('did-fail-load', () => {
mainWindow.reload();
Expand Down
2 changes: 1 addition & 1 deletion ictrl_be.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ def serve(path):
return send_from_directory(app.static_folder, 'index.html')

app.register_blueprint(api, url_prefix='/api')
app.run(host=APP_HOST, port=APP_PORT)
app.run(host=APP_HOST, port=APP_PORT, ssl_context="adhoc")

0 comments on commit ccdfdc9

Please sign in to comment.