From 8ed15f6b79ad8836e98cf69955f03aa98855d4e024d0736134aac65a75d85b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Chv=C3=A1tal?= Date: Mon, 19 Aug 2019 09:46:22 +0000 Subject: [PATCH] - Update to 0.9.0: * Base64 support removed and binary mode is now required * Low level WebSocket protocol handling now has its own class * Authentication now optionally required for web server * Server hostname can be used as the token * JWT/JWS/JWE can be used for the token * redis can be used for the token * Can now log to syslog * Improved latency by disabling Nagle for proxied connection * Added client certificate authentication * Support for password protected certificate key file * TLS ciphers and options are now configurable * Can be invoked via inetd * Lots of minor fixes... - Remove upstream merged: * u_added_jwt_tokens_capability.patch * u_Add-support-for-inetd.patch * u_Fix-inetd-mode-on-python-2.patch * fix-tests-py3.6.patch - Drop suse specific PyJWT-token-plugin.patch, will be easier to actually pull in new dependency on jwcrypto if needed * The tests were failing when using PyJWT... OBS-URL: https://build.opensuse.org/package/show/devel:languages:python/python-websockify?expand=0&rev=38 --- PyJWT-token-plugin.patch | 28 ----- fix-tests-py3.6.patch | 14 --- python-websockify.changes | 26 +++++ python-websockify.spec | 46 ++------ u_Add-support-for-inetd.patch | 173 ---------------------------- u_Fix-inetd-mode-on-python-2.patch | 27 ----- u_added_jwt_tokens_capability.patch | 71 ------------ v0.8.0.tar.gz | 3 - v0.9.0.tar.gz | 3 + 9 files changed, 39 insertions(+), 352 deletions(-) delete mode 100644 PyJWT-token-plugin.patch delete mode 100644 fix-tests-py3.6.patch delete mode 100644 u_Add-support-for-inetd.patch delete mode 100644 u_Fix-inetd-mode-on-python-2.patch delete mode 100644 u_added_jwt_tokens_capability.patch delete mode 100644 v0.8.0.tar.gz create mode 100644 v0.9.0.tar.gz diff --git a/PyJWT-token-plugin.patch b/PyJWT-token-plugin.patch deleted file mode 100644 index e112806..0000000 --- a/PyJWT-token-plugin.patch +++ /dev/null @@ -1,28 +0,0 @@ -From cf2bd3930d8506dbb4db172c89a6a628134595aa Mon Sep 17 00:00:00 2001 -From: UXabre -Date: Fri, 21 Dec 2018 03:21:29 -0500 -Subject: [PATCH] Added JWT tokens capability - -Index: websockify-0.8.0/websockify/token_plugins.py -=================================================================== ---- websockify-0.8.0.orig/websockify/token_plugins.py -+++ websockify-0.8.0/websockify/token_plugins.py -@@ -125,5 +125,17 @@ class JWTTokenApi(BasePlugin): - print >>sys.stderr, "Failed to parse token: %s" % (e) - return None - except ImportError as e: -- print >>sys.stderr, "package jwcrypto not found, are you sure you've installed it correctly?" -+ try: -+ import jwt -+ -+ secret = open(self.source, 'rb').read() -+ parsed = jwt.decode(token, secret, algorithms=['RS256', 'RS384', 'RS512', 'HS256', 'HS384', 'HS512', 'ES256', 'ES384', 'ES512', 'PS256', 'PS384', 'PS512']) -+ -+ return (parsed['host'], parsed['port']) -+ except Exception as e: -+ print >>sys.stderr, "Failed to parse token: %s" % (e) -+ return None -+ except ImportError: -+ print >>sys.stderr, "neither package jwcrypto nor PyJWT found, are you sure you've installed one of them correctly?" -+ return None - return None diff --git a/fix-tests-py3.6.patch b/fix-tests-py3.6.patch deleted file mode 100644 index 95d12de..0000000 --- a/fix-tests-py3.6.patch +++ /dev/null @@ -1,14 +0,0 @@ -Index: websockify-0.8.0+dfsg1/tests/test_websocket.py -=================================================================== ---- websockify-0.8.0+dfsg1.orig/tests/test_websocket.py -+++ websockify-0.8.0+dfsg1/tests/test_websocket.py -@@ -69,6 +69,9 @@ class FakeSocket(object): - else: - return StringIO(self._data.decode('latin_1')) - -+ def sendall(self, data, flags=None): -+ return len(data) -+ - - class WebSocketRequestHandlerTestCase(unittest.TestCase): - def setUp(self): diff --git a/python-websockify.changes b/python-websockify.changes index 8af231e..da8c2c6 100644 --- a/python-websockify.changes +++ b/python-websockify.changes @@ -1,3 +1,29 @@ +------------------------------------------------------------------- +Mon Aug 19 08:56:31 UTC 2019 - Tomáš Chvátal + +- Update to 0.9.0: + * Base64 support removed and binary mode is now required + * Low level WebSocket protocol handling now has its own class + * Authentication now optionally required for web server + * Server hostname can be used as the token + * JWT/JWS/JWE can be used for the token + * redis can be used for the token + * Can now log to syslog + * Improved latency by disabling Nagle for proxied connection + * Added client certificate authentication + * Support for password protected certificate key file + * TLS ciphers and options are now configurable + * Can be invoked via inetd + * Lots of minor fixes... +- Remove upstream merged: + * u_added_jwt_tokens_capability.patch + * u_Add-support-for-inetd.patch + * u_Fix-inetd-mode-on-python-2.patch + * fix-tests-py3.6.patch +- Drop suse specific PyJWT-token-plugin.patch, will be easier + to actually pull in new dependency on jwcrypto if needed + * The tests were failing when using PyJWT... + ------------------------------------------------------------------- Mon Apr 8 14:33:03 UTC 2019 - Cédric Bosdonnat diff --git a/python-websockify.spec b/python-websockify.spec index 9f6a945..424ee06 100644 --- a/python-websockify.spec +++ b/python-websockify.spec @@ -18,47 +18,33 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-websockify -Version: 0.8.0 +Version: 0.9.0 Release: 0 Summary: WebSocket to TCP proxy/bridge License: LGPL-3.0-only AND MPL-2.0 AND BSD-2-Clause AND BSD-3-Clause Group: Development/Languages/Python URL: https://github.com/novnc/websockify Source: https://github.com/novnc/websockify/archive/v%{version}.tar.gz -# PATCH-FEATURE-UPSTREAM u_Add-support-for-inetd.patch fate#323880 msrb@suse.com -- https://github.com/novnc/websockify/pull/293 -Patch1: u_Add-support-for-inetd.patch -# PATCH-FEATURE-UPSTREAM u_Fix-inetd-mode-on-python-2.patch fate#323880 msrb@suse.com -- https://github.com/novnc/websockify/pull/293 -Patch2: u_Fix-inetd-mode-on-python-2.patch -# PATCH-FEATURE-ALMOST-UPSTREAM u_added_jwt_tokens_capability.patch fate#325762 cbosdonnat@suse.com -- https://github.com/novnc/websockify/pull/372 -Patch3: u_added_jwt_tokens_capability.patch -# PATCH-FIX-OPENSUSE PyJWT-token-plugin.patch fate#325762 cbosdonnat@suse.com -- use PyJWT if jwcrypto is missing -Patch4: PyJWT-token-plugin.patch -# PATCH-FROM-UPSTREAM: -Patch5: fix-tests-py3.6.patch -BuildRequires: %{python_module PyJWT} BuildRequires: %{python_module cryptography} +BuildRequires: %{python_module jwcrypto} BuildRequires: %{python_module mox3} BuildRequires: %{python_module nose} BuildRequires: %{python_module numpy} +BuildRequires: %{python_module redis} BuildRequires: %{python_module setuptools} -Requires: python-numpy +BuildRequires: %{python_module simplejson} BuildRequires: fdupes +BuildRequires: python-enum34 BuildRequires: python-rpm-macros +Requires: python-numpy Requires: python-setuptools -Requires: python-websockify-common = %{version} Requires(post): update-alternatives Requires(postun): update-alternatives BuildArch: noarch %if 0%{?suse_version} -# SLES 12 and up to 15SP1 doesn't have python-jwcrypto package and will fallback to -# the PyJWT implementation. However opensuse has jwcrypto since 42.3: use this one -# since it also provides support for JWE (encrypted JWT). -%if 0%{?sle_version} -Recommends: python-PyJWT -Recommends: python-cryptography -%else Recommends: python-jwcrypto -%endif +Recommends: python-redis +Recommends: python-simplejson %endif %python_subpackages @@ -89,16 +75,8 @@ This package contains common files. %prep %setup -q -n websockify-%{version} -%autopatch -p1 - # remove unwanted shebang -sed -i '1 { /^#!/ d }' websockify/websocket*.py -# drop unneeded executable bit -chmod -x include/web-socket-js/web_socket.js -# fix mox3 import -sed -e 's:import stubout:from mox3 import stubout:g' \ - -i tests/test_websocketproxy.py \ - -i tests/test_websocket.py +sed -i '1 { /^#!/ d }' websockify/websock*.py %build %python_build @@ -118,13 +96,9 @@ sed -e 's:import stubout:from mox3 import stubout:g' \ %python_uninstall_alternative websockify %files %{python_files} -%license LICENSE.txt +%license COPYING %doc CHANGES.txt README.md %python_alternative %{_bindir}/websockify %{python_sitelib}/* -%files -n python-websockify-common -%license LICENSE.txt -%{_datadir}/websockify - %changelog diff --git a/u_Add-support-for-inetd.patch b/u_Add-support-for-inetd.patch deleted file mode 100644 index cf5280d..0000000 --- a/u_Add-support-for-inetd.patch +++ /dev/null @@ -1,173 +0,0 @@ -From 1ce74c62c91498f1bf54c030808ba45fb6240aae Mon Sep 17 00:00:00 2001 -From: Michal Srb -Date: Mon, 31 Jul 2017 15:38:52 +0200 -Subject: [PATCH] Add support for inetd. - ---- - websockify/websocketproxy.py | 52 +++++++++++++++++++++++++++--------------- - websockify/websockifyserver.py | 28 +++++++++++++++-------- - 2 files changed, 52 insertions(+), 28 deletions(-) - -Index: websockify-0.8.0/websockify/websocketproxy.py -=================================================================== ---- websockify-0.8.0.orig/websockify/websocketproxy.py -+++ websockify-0.8.0/websockify/websocketproxy.py -@@ -285,12 +285,17 @@ class WebSocketProxy(websocket.WebSocket - else: - dst_string = "%s:%s" % (self.target_host, self.target_port) - -+ if self.listen_fd != None: -+ src_string = "socket %d" % self.listen_fd -+ else: -+ src_string = "%s:%s" % (self.listen_host, self.listen_port) -+ - if self.token_plugin: -- msg = " - proxying from %s:%s to targets generated by %s" % ( -- self.listen_host, self.listen_port, type(self.token_plugin).__name__) -+ msg = " - proxying from %s to targets generated by %s" % ( -+ src_string, type(self.token_plugin).__name__) - else: -- msg = " - proxying from %s:%s to %s" % ( -- self.listen_host, self.listen_port, dst_string) -+ msg = " - proxying from %s to %s" % ( -+ src_string, dst_string) - - if self.ssl_target: - msg += " (using SSL)" -@@ -377,6 +382,8 @@ def websockify_init(): - help="connect to SSL target as SSL client") - parser.add_option("--unix-target", - help="connect to unix socket target", metavar="FILE") -+ parser.add_option("--inetd", -+ help="inetd mode, receive listening socket from stdin", action="store_true") - parser.add_option("--web", default=None, metavar="DIR", - help="run webserver on same port. Serve files from DIR.") - parser.add_option("--wrap-mode", default="exit", metavar="MODE", -@@ -447,15 +454,10 @@ def websockify_init(): - - del opts.target_cfg - -- # Sanity checks -- if len(args) < 2 and not (opts.token_plugin or opts.unix_target): -- parser.error("Too few arguments") - if sys.argv.count('--'): - opts.wrap_cmd = args[1:] - else: - opts.wrap_cmd = None -- if len(args) > 2: -- parser.error("Too many arguments") - - if not websocket.ssl and opts.ssl_target: - parser.error("SSL target requested and Python SSL module not loaded."); -@@ -463,28 +465,42 @@ def websockify_init(): - if opts.ssl_only and not os.path.exists(opts.cert): - parser.error("SSL only and %s not found" % opts.cert) - -- # Parse host:port and convert ports to numbers -- if args[0].count(':') > 0: -- opts.listen_host, opts.listen_port = args[0].rsplit(':', 1) -- opts.listen_host = opts.listen_host.strip('[]') -+ if opts.inetd: -+ opts.listen_fd = sys.stdin.fileno() - else: -- opts.listen_host, opts.listen_port = '', args[0] -+ if len(args) < 1: -+ parser.error("Too few arguments") -+ arg = args.pop(0) -+ # Parse host:port and convert ports to numbers -+ if arg.count(':') > 0: -+ opts.listen_host, opts.listen_port = arg.rsplit(':', 1) -+ opts.listen_host = opts.listen_host.strip('[]') -+ else: -+ opts.listen_host, opts.listen_port = '', arg - -- try: opts.listen_port = int(opts.listen_port) -- except: parser.error("Error parsing listen port") -+ try: opts.listen_port = int(opts.listen_port) -+ except: parser.error("Error parsing listen port") -+ -+ del opts.inetd - - if opts.wrap_cmd or opts.unix_target or opts.token_plugin: - opts.target_host = None - opts.target_port = None - else: -- if args[1].count(':') > 0: -- opts.target_host, opts.target_port = args[1].rsplit(':', 1) -+ if len(args) < 1: -+ parser.error("Too few arguments") -+ arg = args.pop(0) -+ if arg.count(':') > 0: -+ opts.target_host, opts.target_port = arg.rsplit(':', 1) - opts.target_host = opts.target_host.strip('[]') - else: - parser.error("Error parsing target") - try: opts.target_port = int(opts.target_port) - except: parser.error("Error parsing target port") - -+ if len(args) > 0 and opts.wrap_cmd == None: -+ parser.error("Too many arguments") -+ - if opts.token_plugin is not None: - if '.' not in opts.token_plugin: - opts.token_plugin = ( -Index: websockify-0.8.0/websockify/websocket.py -=================================================================== ---- websockify-0.8.0.orig/websockify/websocket.py -+++ websockify-0.8.0/websockify/websocket.py -@@ -601,8 +601,8 @@ class WebSocketServer(object): - class Terminate(Exception): - pass - -- def __init__(self, RequestHandlerClass, listen_host='', -- listen_port=None, source_is_ipv6=False, -+ def __init__(self, RequestHandlerClass, listen_fd=None, -+ listen_host='', listen_port=None, source_is_ipv6=False, - verbose=False, cert='', key='', ssl_only=None, - daemon=False, record='', web='', - file_only=False, -@@ -613,6 +613,7 @@ class WebSocketServer(object): - # settings - self.RequestHandlerClass = RequestHandlerClass - self.verbose = verbose -+ self.listen_fd = listen_fd - self.listen_host = listen_host - self.listen_port = listen_port - self.prefer_ipv6 = source_is_ipv6 -@@ -658,8 +659,11 @@ class WebSocketServer(object): - - # Show configuration - self.msg("WebSocket server settings:") -- self.msg(" - Listen on %s:%s", -- self.listen_host, self.listen_port) -+ if self.listen_fd != None: -+ self.msg(" - Listen on fd %d", self.listen_fd) -+ else: -+ self.msg(" - Listen on %s:%s", -+ self.listen_host, self.listen_port) - self.msg(" - Flash security policy server") - if self.web: - if self.file_only: -@@ -965,12 +969,16 @@ class WebSocketServer(object): - is a WebSockets client then call new_websocket_client() method (which must - be overridden) for each new client connection. - """ -- lsock = self.socket(self.listen_host, self.listen_port, False, -- self.prefer_ipv6, -- tcp_keepalive=self.tcp_keepalive, -- tcp_keepcnt=self.tcp_keepcnt, -- tcp_keepidle=self.tcp_keepidle, -- tcp_keepintvl=self.tcp_keepintvl) -+ -+ if self.listen_fd != None: -+ lsock = socket.fromfd(self.listen_fd, socket.AF_INET, socket.SOCK_STREAM) -+ else: -+ lsock = self.socket(self.listen_host, self.listen_port, False, -+ self.prefer_ipv6, -+ tcp_keepalive=self.tcp_keepalive, -+ tcp_keepcnt=self.tcp_keepcnt, -+ tcp_keepidle=self.tcp_keepidle, -+ tcp_keepintvl=self.tcp_keepintvl) - - if self.daemon: - keepfd = self.get_log_fd() diff --git a/u_Fix-inetd-mode-on-python-2.patch b/u_Fix-inetd-mode-on-python-2.patch deleted file mode 100644 index c1652d6..0000000 --- a/u_Fix-inetd-mode-on-python-2.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 7b382d043e9b85fab2fcddf46a54d19bbd4d601c Mon Sep 17 00:00:00 2001 -From: Michal Srb -Date: Tue, 1 Aug 2017 15:35:34 +0200 -Subject: [PATCH] Fix inetd mode on Python 2. - -In python 2 the ssl.wrap_socket doesn't work on sockets created using socket.fromfd. -The workaround is to wrap the socket returned by socket.fromfd into another socket -object using the private _sock constructor parameter. ---- - websockify/websockifyserver.py | 4 ++++ - 1 file changed, 4 insertions(+) - -Index: websockify-0.8.0/websockify/websocket.py -=================================================================== ---- websockify-0.8.0.orig/websockify/websocket.py -+++ websockify-0.8.0/websockify/websocket.py -@@ -972,6 +972,10 @@ class WebSocketServer(object): - - if self.listen_fd != None: - lsock = socket.fromfd(self.listen_fd, socket.AF_INET, socket.SOCK_STREAM) -+ if sys.hexversion < 0x3000000: -+ # For python 2 we have to wrap the "raw" socket into a socket object, -+ # otherwise ssl wrap_socket doesn't work. -+ lsock = socket.socket(_sock=lsock) - else: - lsock = self.socket(self.listen_host, self.listen_port, False, - self.prefer_ipv6, diff --git a/u_added_jwt_tokens_capability.patch b/u_added_jwt_tokens_capability.patch deleted file mode 100644 index 75e3cd8..0000000 --- a/u_added_jwt_tokens_capability.patch +++ /dev/null @@ -1,71 +0,0 @@ -From 6dc9005930798873ffa714d184312aafd0209503 Mon Sep 17 00:00:00 2001 -From: UXabre -Date: Thu, 17 Jan 2019 08:53:01 -0500 -Subject: [PATCH] Added JWT/JWS/JWE tokens capability - ---- - test-requirements.txt | 1 + - tests/fixtures/private.pem | 27 +++++++++++ - tests/fixtures/public.pem | 9 ++++ - tests/fixtures/symmetric.key | 1 + - tests/test_websocketproxy.py | 90 ++++++++++++++++++++++++++++++++++++ - websockify/token_plugins.py | 46 ++++++++++++++++++ - 6 files changed, 174 insertions(+) - create mode 100644 tests/fixtures/private.pem - create mode 100644 tests/fixtures/public.pem - create mode 100644 tests/fixtures/symmetric.key - -diff --git a/websockify/token_plugins.py b/websockify/token_plugins.py -index e87dcd0..45e974c 100644 ---- a/websockify/token_plugins.py -+++ b/websockify/token_plugins.py -@@ -87,3 +87,49 @@ class JSONTokenApi(BaseTokenAPI): - def process_result(self, resp): - resp_json = resp.json() - return (resp_json['host'], resp_json['port']) -+ -+ -+class JWTTokenApi(BasePlugin): -+ # source is a JWT-token, with hostname and port included -+ # Both JWS as JWE tokens are accepted. With regards to JWE tokens, the key is re-used for both validation and decryption. -+ -+ def lookup(self, token): -+ try: -+ from jwcrypto import jwt -+ import json -+ -+ key = jwt.JWK() -+ -+ try: -+ with open(self.source, 'rb') as key_file: -+ key_data = key_file.read() -+ except Exception as e: -+ print >>sys.stderr, "Error loading key file: %s" % (e) -+ return None -+ -+ try: -+ key.import_from_pem(key_data) -+ except: -+ try: -+ key.import_key(k=key_data,kty='oct') -+ except: -+ print >>sys.stderr, 'Failed to correctly parse key data!' -+ return None -+ -+ try: -+ token = jwt.JWT(key=key, jwt=token) -+ parsed_header = json.loads(token.header) -+ -+ if 'enc' in parsed_header: -+ # Token is encrypted, so we need to decrypt by passing the claims to a new instance -+ token = jwt.JWT(key=key, jwt=token.claims) -+ -+ parsed = json.loads(token.claims) -+ -+ return (parsed['host'], parsed['port']) -+ except Exception as e: -+ print >>sys.stderr, "Failed to parse token: %s" % (e) -+ return None -+ except ImportError as e: -+ print >>sys.stderr, "package jwcrypto not found, are you sure you've installed it correctly?" -+ return None diff --git a/v0.8.0.tar.gz b/v0.8.0.tar.gz deleted file mode 100644 index 998acb3..0000000 --- a/v0.8.0.tar.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f080e40b3f429f39dc557c62c6d715a683100e7c10c557fa376b6dbde23358ce -size 288359 diff --git a/v0.9.0.tar.gz b/v0.9.0.tar.gz new file mode 100644 index 0000000..d1f3ef3 --- /dev/null +++ b/v0.9.0.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ebfec791dd78be6584fb5fe3bc27f02af54501beddf8457368699f571de13ae +size 58493