mirror of
https://github.com/vale981/rmview
synced 2025-03-05 09:11:39 -05:00
Add support for using an SSH tunnel.
This should be preffered approach when connecting to remarkable over (W)LAN for security reasons.
This commit is contained in:
parent
c26efcdead
commit
ba98c0e28b
5 changed files with 126 additions and 16 deletions
19
README.md
19
README.md
|
@ -113,6 +113,7 @@ Connection parameters are provided as a dictionary with the following keys (all
|
|||
| `key` | Local path to key for ssh | not needed if password provided |
|
||||
| `timeout` | Connection timeout in seconds | default: 1 |
|
||||
| `host_key_policy` | `"ask"`, `"ignore_new"`, `"ignore_all"`, `"auto_add"` | default: `"ask"` (description below) |
|
||||
| `tunnel` | True to connect to VNC server over a local SSH tunnel | default: `false` (description below) |
|
||||
|
||||
The `address` parameter can be either:
|
||||
- a single string, in which case the address is used for connection
|
||||
|
@ -121,6 +122,7 @@ The `address` parameter can be either:
|
|||
To establish a connection with the tablet, you can use any of the following:
|
||||
- Leave `auth_method`, `password` and `key` unspecified: this will ask for a password
|
||||
- Specify `"auth_method": "key"` to use a SSH key. In case an SSH key hasn't already been associated with the tablet, you can provide its path with the `key` setting.
|
||||
If key is password protected, you can specify key passphrase using `password` parameter.
|
||||
- Provide a `password` in settings
|
||||
|
||||
If `auth_method` is `password` but no password is specified, then the tool will ask for the password on connection.
|
||||
|
@ -141,7 +143,6 @@ The old `"insecure_auto_add_host": true` parameter is deprecated and equivalent
|
|||
In case your `~/.ssh/known_hosts` file contains the relevant key associations, rMview should pick them up.
|
||||
If you use the "Add/Update" feature when prompted by rMview (for example after a tablet update) then `~/.ssh/known_hosts` will be ignored from then on.
|
||||
|
||||
|
||||
:warning: **Key format error:**
|
||||
If you get an error when connect using a key, but the key seems ok when connecting manually with ssh, you probably need to convert the key to the PEM format (or re-generate it using the `-m PEM` option of `ssh-keygen`). See [here](https://github.com/paramiko/paramiko/issues/340#issuecomment-492448662) for details.
|
||||
|
||||
|
@ -158,6 +159,22 @@ cat ~/.ssh/known_hosts | grep 10.11.99.1 >> ~/.config/rmview_known_hosts
|
|||
|
||||
You should of course replace IP with your remarkable IP.
|
||||
|
||||
### Note on security and using an SSH tunnel
|
||||
|
||||
By default, this program will start VNC server on remarkable which listens on all the interfaces and doesn't expose
|
||||
any authentication mechanism or uses encryption.
|
||||
|
||||
This program will then connect to the VNC server over the IP specified in the config.
|
||||
|
||||
Not using any authentication and exposing VNC server on all the network interfaces may be OK when connecting to the
|
||||
remarkable over USB interface, but when you are connecting to remarkable over WLAN, you are strongly encouraged to
|
||||
use built-in SSH tunnel functionality.
|
||||
|
||||
When SSH tunnel functionality is used, VNC server which is started on remarkable will only listen on localhost, this
|
||||
program will create SSH tunnel to the remarkable and connect to the VNC server over the local SSH tunnel.
|
||||
|
||||
This means that the connection will be encrypted and existing SSH authentication will be used.
|
||||
|
||||
## To Do
|
||||
|
||||
- [ ] Settings dialog
|
||||
|
|
17
example_ssh_key_auth_with_ssh_tunnel.json
Normal file
17
example_ssh_key_auth_with_ssh_tunnel.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"ssh": {
|
||||
"timeout": 4,
|
||||
"address": "192.168.160.100",
|
||||
"username": "root",
|
||||
"auth_method": "key",
|
||||
"key": "/home/user/.ssh/id_rsa_remarkable",
|
||||
"password": "ssh key passphrase",
|
||||
"tunnel": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"pen_size": 15,
|
||||
"pen_color": "red",
|
||||
"pen_trail": 200,
|
||||
"background_color": "white",
|
||||
"hide_pen_on_press": true
|
||||
}
|
2
setup.py
2
setup.py
|
@ -41,7 +41,7 @@ setup(
|
|||
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
|
||||
],
|
||||
packages=['rmview'],
|
||||
install_requires=['pyqt5', 'paramiko', 'twisted'],
|
||||
install_requires=['pyqt5', 'paramiko', 'twisted', 'sshtunnel'],
|
||||
entry_points={
|
||||
'console_scripts':['rmview = rmview.rmview:rmViewMain']
|
||||
},
|
||||
|
|
|
@ -287,7 +287,8 @@ class rMViewApp(QApplication):
|
|||
self.openSettings(prompt=False)
|
||||
return
|
||||
|
||||
self.fbworker = FrameBufferWorker(ssh, delay=self.config.get('fetch_frame_delay'))
|
||||
self.fbworker = FrameBufferWorker(ssh, ssh_config=self.config.get('ssh', {}),
|
||||
delay=self.config.get('fetch_frame_delay'))
|
||||
self.fbworker.signals.onNewFrame.connect(self.onNewFrame)
|
||||
self.fbworker.signals.onFatalError.connect(self.frameError)
|
||||
self.threadpool.start(self.fbworker)
|
||||
|
|
|
@ -5,13 +5,14 @@ from PyQt5.QtCore import *
|
|||
from .rmparams import *
|
||||
|
||||
import paramiko
|
||||
import sshtunnel
|
||||
import struct
|
||||
import time
|
||||
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
|
||||
import atexit
|
||||
|
||||
from twisted.internet.protocol import Protocol
|
||||
from twisted.internet import protocol, reactor
|
||||
|
@ -79,43 +80,117 @@ class FrameBufferWorker(QRunnable):
|
|||
|
||||
_stop = False
|
||||
|
||||
def __init__(self, ssh, delay=None, lz4_path=None, img_format=IMG_FORMAT):
|
||||
def __init__(self, ssh, ssh_config, delay=None, lz4_path=None, img_format=IMG_FORMAT):
|
||||
super(FrameBufferWorker, self).__init__()
|
||||
self.ssh = ssh
|
||||
self.ssh_config = ssh_config
|
||||
self.img_format = img_format
|
||||
self.use_ssh_tunnel = self.ssh_config.get("tunnel", False)
|
||||
|
||||
self.vncClient = None
|
||||
self.sshTunnel = None
|
||||
|
||||
self.signals = FBWSignals()
|
||||
|
||||
def stop(self):
|
||||
if self._stop:
|
||||
# Already stopped
|
||||
return
|
||||
|
||||
self._stop = True
|
||||
|
||||
log.info("Stopping framebuffer thread...")
|
||||
reactor.callFromThread(reactor.stop)
|
||||
|
||||
try:
|
||||
log.info("Stopping VNC server...")
|
||||
self.ssh.exec_command("killall rM-vnc-server-standalone", timeout=3)
|
||||
except Exception as e:
|
||||
log.warning("VNC could not be stopped on the reMarkable.")
|
||||
log.warning("Although this is not a big problem, it may consume some resources until you restart the tablet.")
|
||||
log.warning("You can manually terminate it by running `ssh %s killall rM-vnc-server-standalone`.", self.ssh.hostname)
|
||||
log.error(e)
|
||||
|
||||
if self.sshTunnel:
|
||||
try:
|
||||
log.info("Stopping SSH tunnel...")
|
||||
self.sshTunnel.stop()
|
||||
except Exception as e:
|
||||
log.error(e)
|
||||
|
||||
log.info("Framebuffer thread stopped")
|
||||
|
||||
@pyqtSlot()
|
||||
def run(self):
|
||||
log.info("Starting VNC server")
|
||||
# On start up we try to kill any previous "stray" running VNC server processes
|
||||
try:
|
||||
_,_,out = self.ssh.exec_command("$HOME/rM-vnc-server-standalone")
|
||||
log.info(next(out))
|
||||
self.ssh.exec_command("killall rM-vnc-server-standalone", timeout=3)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# If using SSH tunnel, we ensure VNC server only listens on localhost
|
||||
if self.use_ssh_tunnel:
|
||||
server_run_cmd = "$HOME/rM-vnc-server-standalone -listen localhost"
|
||||
else:
|
||||
server_run_cmd = "$HOME/rM-vnc-server-standalone"
|
||||
|
||||
log.info("Starting VNC server (command=%s)" % (server_run_cmd))
|
||||
|
||||
try:
|
||||
_,_,out = self.ssh.exec_command(server_run_cmd)
|
||||
log.info("Command output: %s" % (next(out)))
|
||||
except Exception as e:
|
||||
self.signals.onFatalError.emit(e)
|
||||
|
||||
while self._stop == False:
|
||||
log.info("Establishing connection to remote VNC server")
|
||||
try:
|
||||
self.vncClient = internet.TCPClient(self.ssh.hostname, 5900, RFBFactory(self.signals))
|
||||
self.vncClient.startService()
|
||||
reactor.run(installSignalHandlers=0)
|
||||
except Exception as e:
|
||||
log.error(e)
|
||||
# Register atexit handler to ensure we always try to kill started server on exit
|
||||
atexit.register(self.stop)
|
||||
|
||||
if self.use_ssh_tunnel:
|
||||
tunnel = self._get_ssh_tunnel()
|
||||
tunnel.start()
|
||||
|
||||
self.sshTunnel = tunnel
|
||||
|
||||
log.info("Setting up SSH tunnel %s:%s <-> %s:%s" % ("127.0.0.1", 5900, tunnel.local_bind_host,
|
||||
tunnel.local_bind_port))
|
||||
|
||||
vnc_server_host = tunnel.local_bind_host
|
||||
vnc_server_port = tunnel.local_bind_port
|
||||
else:
|
||||
vnc_server_host = self.ssh.hostname
|
||||
vnc_server_port = 5900
|
||||
|
||||
while not self._stop:
|
||||
log.info("Establishing connection to remote VNC server to %s:%s" % (vnc_server_host,
|
||||
vnc_server_port))
|
||||
try:
|
||||
self.vncClient = internet.TCPClient(vnc_server_host, vnc_server_port, RFBFactory(self.signals))
|
||||
self.vncClient.startService()
|
||||
reactor.run(installSignalHandlers=0)
|
||||
except Exception as e:
|
||||
log.error("Failed to connect to the VNC server: %s" % (str(e)))
|
||||
|
||||
def _get_ssh_tunnel(self):
|
||||
open_tunnel_kwargs = {
|
||||
"ssh_username" : self.ssh_config.get("username", "root"),
|
||||
}
|
||||
|
||||
if self.ssh_config.get("auth_method", "password") == "key":
|
||||
open_tunnel_kwargs["ssh_pkey"] = self.ssh_config["key"]
|
||||
|
||||
if self.ssh_config.get("password", None):
|
||||
open_tunnel_kwargs["ssh_private_key_password"] = self.ssh_config["password"]
|
||||
else:
|
||||
open_tunnel_kwargs["ssh_password"] = self.ssh_config["password"]
|
||||
|
||||
tunnel = sshtunnel.open_tunnel(
|
||||
(self.ssh.hostname, 22),
|
||||
remote_bind_address=("127.0.0.1", 5900),
|
||||
# We don't specify port so library auto assigns random unused one in the high range
|
||||
local_bind_address=('127.0.0.1',),
|
||||
**open_tunnel_kwargs)
|
||||
|
||||
return tunnel
|
||||
|
||||
|
||||
class PWSignals(QObject):
|
||||
|
|
Loading…
Add table
Reference in a new issue