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:
Dominique Leuenberger 2019-08-27 13:21:12 +00:00 committed by Git OBS Bridge
commit bb6cd4bcf0
9 changed files with 39 additions and 352 deletions

View File

@ -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

View File

@ -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):

View File

@ -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>

View File

@ -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

View File

@ -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()

View File

@ -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,

View File

@ -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

View File

@ -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
View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6ebfec791dd78be6584fb5fe3bc27f02af54501beddf8457368699f571de13ae
size 58493