Accepting request 724540 from devel:languages:python
- 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/request/show/724540 OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python-websockify?expand=0&rev=20
This commit is contained in:
commit
bb6cd4bcf0
@ -1,28 +0,0 @@
|
||||
From cf2bd3930d8506dbb4db172c89a6a628134595aa Mon Sep 17 00:00:00 2001
|
||||
From: UXabre <arend.lapere@gmail.com>
|
||||
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
|
@ -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):
|
@ -1,3 +1,29 @@
|
||||
-------------------------------------------------------------------
|
||||
Mon Aug 19 08:56:31 UTC 2019 - Tomáš Chvátal <tchvatal@suse.com>
|
||||
|
||||
- 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 <cbosdonnat@suse.com>
|
||||
|
||||
|
@ -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
|
||||
|
@ -1,173 +0,0 @@
|
||||
From 1ce74c62c91498f1bf54c030808ba45fb6240aae Mon Sep 17 00:00:00 2001
|
||||
From: Michal Srb <msrb@suse.com>
|
||||
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()
|
@ -1,27 +0,0 @@
|
||||
From 7b382d043e9b85fab2fcddf46a54d19bbd4d601c Mon Sep 17 00:00:00 2001
|
||||
From: Michal Srb <msrb@suse.com>
|
||||
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,
|
@ -1,71 +0,0 @@
|
||||
From 6dc9005930798873ffa714d184312aafd0209503 Mon Sep 17 00:00:00 2001
|
||||
From: UXabre <arend.lapere@gmail.com>
|
||||
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
|
@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f080e40b3f429f39dc557c62c6d715a683100e7c10c557fa376b6dbde23358ce
|
||||
size 288359
|
3
v0.9.0.tar.gz
Normal file
3
v0.9.0.tar.gz
Normal file
@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6ebfec791dd78be6584fb5fe3bc27f02af54501beddf8457368699f571de13ae
|
||||
size 58493
|
Loading…
x
Reference in New Issue
Block a user