commit 827a0d8a9dd10f78abfd83ebd8d58c31aba81c01
parent deef92fe6656181fd600501cc842683454efd38f
Author: Sheng <webmaster0115@gmail.com>
Date: Sun, 14 Oct 2018 16:15:39 +0800
Support https server
Diffstat:
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