webssh

Web based ssh client https://github.com/huashengdun/webssh webssh.huashengdun.org/
git clone http://git.hanabi.in/repos/webssh.git
Log | Files | Refs | README | LICENSE

commit 827a0d8a9dd10f78abfd83ebd8d58c31aba81c01
parent deef92fe6656181fd600501cc842683454efd38f
Author: Sheng <webmaster0115@gmail.com>
Date:   Sun, 14 Oct 2018 16:15:39 +0800

Support https server

Diffstat:
Atests/data/cert.crt | 21+++++++++++++++++++++
Atests/data/cert.key | 28++++++++++++++++++++++++++++
Mtests/test_settings.py | 44+++++++++++++++++++++++++++++++++++++++++++-
Mwebssh/main.py | 16++++++++++++----
Mwebssh/settings.py | 23+++++++++++++++++++++++
5 files changed, 127 insertions(+), 5 deletions(-)

diff --git a/tests/data/cert.crt b/tests/data/cert.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDYDCCAkigAwIBAgIJAPPORA/o2Zd4MA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTgxMDE0MDgwNTQzWhcNMjExMDEzMDgwNTQzWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAvSFaffq6ExFCPN4cApRopGEqVIipAYb6Ky3VHVu4pW0tOdrdKafGGYkN +GWQdsLV0AAzzxmCAPpXmmAx0m0mgtPaJp3iW8NUibkISxdEO/QJOA7y8O9iWhDdb +l9ghjwPI5AwURQkDkXbcBBBzQksYDaYseL2NGDGXkKCUQQoLzV0H+SV3vCPrbOXH +t50HKgKzEOGoT8LcI7BRCTXk1xTlK0b/4ylKUwKIsfNPH0a9RkukBjMFkpXG/2CV +VWb89+TkMzQwhcpIVn6rUCJQW5pHVRYLACP32Zki7xPUJb9OfF7XDK54v6Cwo3Fi +aZWxN6rYhnn8wRTufY3PYzv5f3XiZwIDAQABo1MwUTAdBgNVHQ4EFgQUq0kfpU/m +WQwNk3ymwm7fuVwYhJ0wHwYDVR0jBBgwFoAUq0kfpU/mWQwNk3ymwm7fuVwYhJ0w +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAf2xudhAeOTUpNpw+ +XZWLBXBKZXINd7PrUDgEG4bB0/0kYZN+T7bMJEtmv6+9t57y6jSni9sQzpbvT2tJ +TrbZgwhDvyTm3mw5n5RpAB9ZK+lnMcasa5N4qSd6wmpXjkC+kcEs7oQ8PwgIf3xT +/aGdoswNTWCz0W8vs8yRynLB4MKx1d20IMlDkfGu5n7wXhNK0ymcT8pa6iqEYl6X +bhPVTlELl8bM/OKktFc42VXoRghLRnfl8yM/9t7HVHKfHXZrLpIdtEOvnKwtzX5r +fBMs4IPa0OIPHGCcbLGT4rIbSvSaI8yOPA93G1XXbMF1VKdKyzdGjMS6aFKfbrhV +lnaUOA== +-----END CERTIFICATE----- diff --git a/tests/data/cert.key b/tests/data/cert.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC9IVp9+roTEUI8 +3hwClGikYSpUiKkBhvorLdUdW7ilbS052t0pp8YZiQ0ZZB2wtXQADPPGYIA+leaY +DHSbSaC09omneJbw1SJuQhLF0Q79Ak4DvLw72JaEN1uX2CGPA8jkDBRFCQORdtwE +EHNCSxgNpix4vY0YMZeQoJRBCgvNXQf5JXe8I+ts5ce3nQcqArMQ4ahPwtwjsFEJ +NeTXFOUrRv/jKUpTAoix808fRr1GS6QGMwWSlcb/YJVVZvz35OQzNDCFykhWfqtQ +IlBbmkdVFgsAI/fZmSLvE9Qlv058XtcMrni/oLCjcWJplbE3qtiGefzBFO59jc9j +O/l/deJnAgMBAAECggEAZSwcblvbgiuvVUQzk6W0PIrFzCa20dxUoxiHcocIRWYb +1WEhAhF/xVUtLrIBt++5N/W1yh8BO3mQuzGehxth3qwrguzdQcOiAX1S8YMeE3ZS +KWmjABiim+PJGXdCrHCH3IYhqbRitkPw+jOalJH7MgH8tDIh8hlFTNa5t/kZyybW +uGFbqF6OFmyHSDIPvjPALzSlmd5po+EywnA5oa3sObj4n5xuaFB2l/IaF3ix38vT +geo517L15cCuAa7x42i1cAGn5H/hdeO/Dw+MGk+0sXRRPooCMBzKztxpsB+7kNhk +jbsVHmTkE5UG/T7Uc0PsthZNjFwouPOrQQVUFYTnwQKBgQDwBvpmc9vX4gnADa7p +L2lgMVo6KccPFeFr4DIAYmwS0Vl0sB2j6nPVEBg3PatGLKGNMCIlcj+A3z6KQ+4o +n7pnekRwX+2+m3OPX4Rbw8c/+E0CiRPtmYp9BISKNgPoSRGsI6s/L3wzagsDsQ3v +xhKCohvfyY8JwUEPX6Hosmu/UQKBgQDJt0/ihWn0g/2uOKnXlXthxvkXFoR45sO7 +lY/yoyJB+Z4yGAjJlbyra+5xnReqYyBnf34/2AoddjT45dPCaFucMInQFINdMGF1 +NeVNzC6xa/7jjbgwf4kGqHsLC85Mrq3wyK5hwhMmfEPmRs6w+CRzM/Q78Bsr5P/T +zEa13jFINwKBgQC50L0ieUjVDKD9s9oXnWOXWz19T4BRtl+nco1i7M67lqQJCJo5 +njQD2ozUnwIrtjtuoLeeg56Ttr+krEf/3P+iQe4fjLPxXkiM0qYVoC9s311GvDXY +N4gVllzA3mYR+hcbSxW0OZ+N8ecK+ZNPbug/hx3LFi+MnrYuH5upGA7/sQKBgCRk +nlUQHP2wkqRMNNhgb9JEQ8yWk2/8snO1mDL+m7+reY8wJuW3zkJfRrXY0dw75izG +I9EA+VI3cXc2f+4jReP4HeUczlaR1AOBpc1TeVkpUuNbPlABsocw/oIPrzjGiztV ++aBJk4ruAJIbVE85ddoTFY161Gwm9MERqfBGFj4hAoGAN/ry0KC9/QkLkuPjs3uL +AU3xjBJt1SMB7KZq1yt8mBo8M4q/E3ulynBK7G3f+hS2aj7OAhU4IcPRPGqjsLO1 +dZTIOMeVyOAr0TAaioCCIyvf8hEjA7cXddnWBJYi3WiUpOc6J0uINoSlrAX2UXtw +/Aq5PmJKn4D4a75f+ue2Sw8= +-----END PRIVATE KEY----- diff --git a/tests/test_settings.py b/tests/test_settings.py @@ -1,4 +1,5 @@ import io +import ssl import sys import os.path import unittest @@ -8,7 +9,8 @@ import tornado.options as options from tests.utils import make_tests_data_path from webssh.policy import load_host_keys from webssh.settings import ( - get_host_keys_settings, get_policy_setting, base_dir, print_version + get_host_keys_settings, get_policy_setting, base_dir, print_version, + get_ssl_context ) from webssh.utils import UnicodeType from webssh._version import __version__ @@ -78,3 +80,43 @@ class TestSettings(unittest.TestCase): ) else: self.assertIsInstance(instance, paramiko.client.RejectPolicy) + + def test_get_ssl_context(self): + options.certfile = '' + options.keyfile = '' + ssl_ctx = get_ssl_context(options) + self.assertIsNone(ssl_ctx) + + options.certfile = 'provided' + options.keyfile = '' + with self.assertRaises(ValueError) as ctx: + ssl_ctx = get_ssl_context(options) + self.assertEqual('keyfile is not provided', str(ctx.exception)) + + options.certfile = '' + options.keyfile = 'provided' + with self.assertRaises(ValueError) as ctx: + ssl_ctx = get_ssl_context(options) + self.assertEqual('certfile is not provided', str(ctx.exception)) + + options.certfile = 'FileDoesNotExist' + options.keyfile = make_tests_data_path('cert.key') + with self.assertRaises(ValueError) as ctx: + ssl_ctx = get_ssl_context(options) + self.assertIn('does not exist', str(ctx.exception)) + + options.certfile = make_tests_data_path('cert.key') + options.keyfile = 'FileDoesNotExist' + with self.assertRaises(ValueError) as ctx: + ssl_ctx = get_ssl_context(options) + self.assertIn('does not exist', str(ctx.exception)) + + options.certfile = make_tests_data_path('cert.key') + options.keyfile = make_tests_data_path('cert.key') + with self.assertRaises(ssl.SSLError) as ctx: + ssl_ctx = get_ssl_context(options) + + options.certfile = make_tests_data_path('cert.crt') + options.keyfile = make_tests_data_path('cert.key') + ssl_ctx = get_ssl_context(options) + self.assertIsNotNone(ssl_ctx) diff --git a/webssh/main.py b/webssh/main.py @@ -4,8 +4,10 @@ import tornado.ioloop from tornado.options import options from webssh.handler import IndexHandler, WsockHandler -from webssh.settings import (get_app_settings, get_host_keys_settings, - get_policy_setting, max_body_size) +from webssh.settings import ( + get_app_settings, get_host_keys_settings, get_policy_setting, + get_ssl_context, max_body_size, xheaders +) def make_handlers(loop, options): @@ -28,9 +30,15 @@ def main(): options.parse_command_line() loop = tornado.ioloop.IOLoop.current() app = make_app(make_handlers(loop, options), get_app_settings(options)) - server_settings = dict(xheaders=True, max_body_size=max_body_size) - app.listen(options.port, options.address, **server_settings) + ssl_ctx = get_ssl_context(options) + kwargs = dict(xheaders=xheaders, max_body_size=max_body_size) + app.listen(options.port, options.address, **kwargs) logging.info('Listening on {}:{}'.format(options.address, options.port)) + if ssl_ctx: + kwargs.update(ssl_options=ssl_ctx) + app.listen(options.sslPort, options.sslAddress, **kwargs) + logging.info('Listening on ssl {}:{}'.format(options.sslAddress, + options.sslPort)) loop.start() diff --git a/webssh/settings.py b/webssh/settings.py @@ -1,5 +1,6 @@ import logging import os.path +import ssl import sys from tornado.options import define @@ -17,6 +18,10 @@ def print_version(flag): define('address', default='127.0.0.1', help='Listen address') define('port', type=int, default=8888, help='Listen port') +define('sslAddress', default='0.0.0.0', help='SSL listen address') +define('sslPort', type=int, default=4433, help='SSL listen port') +define('certfile', default='', help='SSL certificate file') +define('keyfile', default='', help='SSL key file') define('debug', type=bool, default=False, help='Debug mode') define('policy', default='warning', help='Missing host key policy, reject|autoadd|warning') @@ -30,6 +35,7 @@ define('version', type=bool, help='Show version information', base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) max_body_size = 1 * 1024 * 1024 swallow_http_errors = True +xheaders = True def get_app_settings(options): @@ -69,3 +75,20 @@ def get_policy_setting(options, host_keys_settings): logging.info(policy_class.__name__) check_policy_setting(policy_class, host_keys_settings) return policy_class() + + +def get_ssl_context(options): + if not options.certfile and not options.keyfile: + return None + elif not options.certfile: + raise ValueError('certfile is not provided') + elif not options.keyfile: + raise ValueError('keyfile is not provided') + elif not os.path.isfile(options.certfile): + raise ValueError('File {!r} does not exist'.format(options.certfile)) + elif not os.path.isfile(options.keyfile): + raise ValueError('File {!r} does not exist'.format(options.keyfile)) + else: + ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + ssl_ctx.load_cert_chain(options.certfile, options.keyfile) + return ssl_ctx