Introduce Auth Plugins
Auth plugins provide a generic interface for authenticating requests. The plugin name is specified using the '--auth-plugin' option, and may either be the name of a class from `websockify.auth_plugins`, or a fully qualified python path to the auth plugin class (see below). An optional plugin parameter can be specified using the '--auth-source' option (a value of `None` will be used if no '--auth-source' option is specified). Auth plugins should inherit from `websockify.auth_plugins.BasePlugin`, and should implement the `authenticate(headers, target_host, target_port)` method. The value of the '--auth-source' option is available as `self.source`. One plugin is currently included: `ExpectOrigin`. The `ExpectOrigin` plugin checks that the 'Origin' header is an expected value. The list of acceptable origins is passed using the plugin source, as a space-separated list.
This commit is contained in:
parent
d94b944f93
commit
df10501615
|
|
@ -0,0 +1,33 @@
|
||||||
|
class BasePlugin(object):
|
||||||
|
def __init__(self, src=None):
|
||||||
|
self.source = src
|
||||||
|
|
||||||
|
def authenticate(self, headers, target_host, target_port):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AuthenticationError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidOriginError(AuthenticationError):
|
||||||
|
def __init__(self, expected, actual):
|
||||||
|
self.expected_origin = expected
|
||||||
|
self.actual_origin = actual
|
||||||
|
|
||||||
|
super(InvalidOriginError, self).__init__(
|
||||||
|
"Invalid Origin Header: Expected one of "
|
||||||
|
"%s, got '%s'" % (expected, actual))
|
||||||
|
|
||||||
|
|
||||||
|
class ExpectOrigin(object):
|
||||||
|
def __init__(self, src=None):
|
||||||
|
if src is None:
|
||||||
|
self.source = []
|
||||||
|
else:
|
||||||
|
self.source = src.split()
|
||||||
|
|
||||||
|
def authenticate(self, headers, target_host, target_port):
|
||||||
|
origin = headers.getheader('Origin', None)
|
||||||
|
if origin is None or origin not in self.source:
|
||||||
|
raise InvalidOriginError(expected=self.source, actual=origin)
|
||||||
|
|
@ -47,6 +47,11 @@ Traffic Legend:
|
||||||
if self.server.token_plugin:
|
if self.server.token_plugin:
|
||||||
(self.server.target_host, self.server.target_port) = self.get_target(self.server.token_plugin, self.path)
|
(self.server.target_host, self.server.target_port) = self.get_target(self.server.token_plugin, self.path)
|
||||||
|
|
||||||
|
if self.server.auth_plugin:
|
||||||
|
self.server.auth_plugin.authenticate(
|
||||||
|
headers=self.headers, target_host=self.server.target_host,
|
||||||
|
target_port=self.server.target_port)
|
||||||
|
|
||||||
# Connect to the target
|
# Connect to the target
|
||||||
if self.server.wrap_cmd:
|
if self.server.wrap_cmd:
|
||||||
msg = "connecting to command: '%s' (port %s)" % (" ".join(self.server.wrap_cmd), self.server.target_port)
|
msg = "connecting to command: '%s' (port %s)" % (" ".join(self.server.wrap_cmd), self.server.target_port)
|
||||||
|
|
@ -197,6 +202,9 @@ class WebSocketProxy(websocket.WebSocketServer):
|
||||||
token_plugin = kwargs.pop('token_plugin', None)
|
token_plugin = kwargs.pop('token_plugin', None)
|
||||||
token_source = kwargs.pop('token_source', None)
|
token_source = kwargs.pop('token_source', None)
|
||||||
|
|
||||||
|
auth_plugin = kwargs.pop('auth_plugin', None)
|
||||||
|
auth_source = kwargs.pop('auth_source', None)
|
||||||
|
|
||||||
if token_plugin is not None:
|
if token_plugin is not None:
|
||||||
if '.' not in token_plugin:
|
if '.' not in token_plugin:
|
||||||
token_plugin = 'websockify.token_plugins.%s' % token_plugin
|
token_plugin = 'websockify.token_plugins.%s' % token_plugin
|
||||||
|
|
@ -210,6 +218,19 @@ class WebSocketProxy(websocket.WebSocketServer):
|
||||||
else:
|
else:
|
||||||
self.token_plugin = None
|
self.token_plugin = None
|
||||||
|
|
||||||
|
if auth_plugin is not None:
|
||||||
|
if '.' not in auth_plugin:
|
||||||
|
auth_plugin = 'websockify.auth_plugins.%s' % auth_plugin
|
||||||
|
|
||||||
|
auth_plugin_module, auth_plugin_cls = auth_plugin.rsplit('.', 1)
|
||||||
|
|
||||||
|
__import__(auth_plugin_module)
|
||||||
|
auth_plugin_cls = getattr(sys.modules[auth_plugin_module], auth_plugin_cls)
|
||||||
|
|
||||||
|
self.auth_plugin = auth_plugin_cls(auth_source)
|
||||||
|
else:
|
||||||
|
self.auth_plugin = None
|
||||||
|
|
||||||
# Last 3 timestamps command was run
|
# Last 3 timestamps command was run
|
||||||
self.wrap_times = [0, 0, 0]
|
self.wrap_times = [0, 0, 0]
|
||||||
|
|
||||||
|
|
@ -381,6 +402,12 @@ def websockify_init():
|
||||||
parser.add_option("--token-source", default=None, metavar="ARG",
|
parser.add_option("--token-source", default=None, metavar="ARG",
|
||||||
help="an argument to be passed to the token plugin"
|
help="an argument to be passed to the token plugin"
|
||||||
"on instantiation")
|
"on instantiation")
|
||||||
|
parser.add_option("--auth-plugin", default=None, metavar="PLUGIN",
|
||||||
|
help="use the given Python class to determine if "
|
||||||
|
"a connection is allowed")
|
||||||
|
parser.add_option("--auth-source", default=None, metavar="ARG",
|
||||||
|
help="an argument to be passed to the auth plugin"
|
||||||
|
"on instantiation")
|
||||||
parser.add_option("--auto-pong", action="store_true",
|
parser.add_option("--auto-pong", action="store_true",
|
||||||
help="Automatically respond to ping frames with a pong")
|
help="Automatically respond to ping frames with a pong")
|
||||||
parser.add_option("--heartbeat", type=int, default=0,
|
parser.add_option("--heartbeat", type=int, default=0,
|
||||||
|
|
@ -394,6 +421,10 @@ def websockify_init():
|
||||||
if opts.token_source and not opts.token_plugin:
|
if opts.token_source and not opts.token_plugin:
|
||||||
parser.error("You must use --token-plugin to use --token-source")
|
parser.error("You must use --token-plugin to use --token-source")
|
||||||
|
|
||||||
|
if opts.auth_source and not opts.auth_plugin:
|
||||||
|
parser.error("You must use --auth-plugin to use --auth-source")
|
||||||
|
|
||||||
|
|
||||||
# Transform to absolute path as daemon may chdir
|
# Transform to absolute path as daemon may chdir
|
||||||
if opts.target_cfg:
|
if opts.target_cfg:
|
||||||
opts.target_cfg = os.path.abspath(opts.target_cfg)
|
opts.target_cfg = os.path.abspath(opts.target_cfg)
|
||||||
|
|
@ -471,9 +502,11 @@ class LibProxyServer(ForkingMixIn, HTTPServer):
|
||||||
self.ssl_target = kwargs.pop('ssl_target', None)
|
self.ssl_target = kwargs.pop('ssl_target', None)
|
||||||
self.token_plugin = kwargs.pop('token_plugin', None)
|
self.token_plugin = kwargs.pop('token_plugin', None)
|
||||||
self.token_source = kwargs.pop('token_source', None)
|
self.token_source = kwargs.pop('token_source', None)
|
||||||
|
self.auth_plugin = kwargs.pop('auth_plugin', None)
|
||||||
self.heartbeat = kwargs.pop('heartbeat', None)
|
self.heartbeat = kwargs.pop('heartbeat', None)
|
||||||
|
|
||||||
self.token_plugin = None
|
self.token_plugin = None
|
||||||
|
self.auth_plugin = None
|
||||||
self.daemon = False
|
self.daemon = False
|
||||||
|
|
||||||
# Server configuration
|
# Server configuration
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue