From 308e3f6ffdd5fa966b7cc6f5ca721ef6c1d4b4fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20K=C3=A1cha?= <ph@cesnet.cz> Date: Mon, 13 Jun 2016 18:11:19 +0200 Subject: [PATCH] PlainAuthenticator (forego X509 for testing) (thx to Radko Krkos) --- warden3/warden_server/README | 11 ++- warden3/warden_server/warden_server.py | 115 +++++++++++-------------- 2 files changed, 57 insertions(+), 69 deletions(-) diff --git a/warden3/warden_server/README b/warden3/warden_server/README index 92a0180..104fb61 100644 --- a/warden3/warden_server/README +++ b/warden3/warden_server/README @@ -37,7 +37,7 @@ B. Dependencies 1. Platform Python 2.7+ - Apache 2.2 + Apache 2.2/2.4 mod_wsgi 3.3+ 2. Python modules @@ -123,7 +123,7 @@ particular implementation object of the aspect, for example type of logger Log: FileLogger, SysLogger DB: MySQL - Auth: X509Authenticator, NoAuthenticator + Auth: X509Authenticator, PlainAuthenticator Validator: JSONSchemaValidator, NoValidator Handler: WardenHandler @@ -142,12 +142,15 @@ object from particular section list is used ("FileLogger" for example). facility: syslog facility, defaults to "daemon" level: least log level (CRITICAL, ERROR, WARNING, INFO, DEBUG) - NoAuthenticator: forego authentication, for debug purposes - X509Authenticator: authenticate based on certificate chain validation, hostname corresponding with certificate CN or SubjectAltName and optionally shared secret + PlainAuthenticator: authenticate based on client name or shared secret, usable + over plain HTTP connection or HTTPS without client certificate - note that + this pretty much spoils security, and is meant only for testing and + debugging purposes, NOT for production servers + NoValidator: forego event JSON validation, for debug purposes JSONSchemaValidator: validate incoming events based on JSON schema file diff --git a/warden3/warden_server/warden_server.py b/warden3/warden_server/warden_server.py index 462f70f..3f29de1 100755 --- a/warden3/warden_server/warden_server.py +++ b/warden3/warden_server/warden_server.py @@ -272,44 +272,63 @@ class ObjectReq(Object): -class NoAuthenticator(ObjectReq): +class PlainAuthenticator(ObjectReq): - def __init__(self, req): + def __init__(self, req, db): ObjectReq.__init__(self, req) + self.db = db - def shash(self, s): - """ Simple FNV1 hash for creating ids on the fly """ - res = 2166136261 - for c in s: - res = 0xFFFFFFFF & res * 16777619 ^ ord(c) - return res + def __str__(self): + return "%s(req=%s, db=%s)" % (type(self).__name__, type(self.req).__name__, type(self.db).__name__) def authenticate(self, env, args): name = args.get("client", [None])[0] - if name is None: - logging.error("NoAuthenticator: clients must authenticate by name, not secret") + secret = args.get("secret", [None])[0] + hostnames = args.get("hostnames", [None])[0] + + client = self.db.get_client_by_name(hostnames, name, secret) + + if not client: + logging.info("authenticate: client not found by name: \"%s\", secret: %s, hostnames: %s" % ( + name, secret, str(hostnames))) return None - return Client(self.shash(name), None, None, None, name, None, 1, None, 1, 1, 1, 0) + # Clients with 'secret' set must get authenticated by it. + # No secret turns secret auth off for this particular client. + if client.secret is not None and secret is None: + logging.info("authenticate: missing secret argument") + return None + logging.info("authenticate: %s" % str(client)) - def authorize(self, env, client, path, method): - return (client is not None) + return client + def authorize(self, env, client, path, method): + if method.debug: + if not client.debug: + logging.info("authorize: failed, client does not have debug enabled") + return None + return client -class X509Authenticator(NoAuthenticator): + if method.read: + if not client.read: + logging.info("authorize: failed, client does not have read enabled") + return None + return client - def __init__(self, req, db): - NoAuthenticator.__init__(self, req) - self.db = db + if method.write: + if not (client.write or client.test): + logging.info("authorize: failed, client is not allowed to write or test") + return None + + return client - def __str__(self): - return "%s(req=%s, db=%s)" % (type(self).__name__, type(self.req).__name__, type(self.db).__name__) +class X509Authenticator(PlainAuthenticator): def get_cert_dns_names(self, pem): @@ -330,7 +349,7 @@ class X509Authenticator(NoAuthenticator): return [firstcommon] + list(set(altnames+commons) - set([firstcommon])) - def authenticate (self, env, args): + def authenticate(self, env, args): try: cert_names = self.get_cert_dns_names(env["SSL_CLIENT_CERT"]) except: @@ -338,46 +357,9 @@ class X509Authenticator(NoAuthenticator): exception.log(logging.getLogger()) return None - name = args.get("client", [None])[0] - secret = args.get("secret", [None])[0] + args["hostnames"] = [cert_names] - client = self.db.get_client_by_name(cert_names, name, secret) - - if not client: - logging.info("authenticate: client not found by name: \"%s\", secret: %s, cert_names: %s" % ( - name, secret, str(cert_names))) - return None - - # Clients with 'secret' set must get authenticated by it. - # No secret turns secret auth off for this particular client. - if client.secret is not None and secret is None: - logging.info("authenticate: missing secret argument") - return None - - logging.info("authenticate: %s" % str(client)) - - return client - - - def authorize(self, env, client, path, method): - if method.debug: - if not client.debug: - logging.info("authorize: failed, client does not have debug enabled") - return None - return client - - if method.read: - if not client.read: - logging.info("authorize: failed, client does not have read enabled") - return None - return client - - if method.write: - if not (client.write or client.test): - logging.info("authorize: failed, client is not allowed to write or test") - return None - - return client + return PlainAuthenticator.authenticate(self, env, args) class NoValidator(ObjectReq): @@ -510,7 +492,7 @@ class MySQL(ObjectReq): return "" if b else "NOT" - def get_client_by_name(self, cert_names, name=None, secret=None): + def get_client_by_name(self, cert_names=None, name=None, secret=None): query = ["SELECT * FROM clients WHERE valid = 1"] params = [] if name: @@ -519,8 +501,9 @@ class MySQL(ObjectReq): if secret: query.append(" AND secret = %s") params.append(secret) - query.append(" AND hostname IN (%s)" % self._get_comma_perc(cert_names)) - params.extend(n.lower() for n in cert_names) + if cert_names: + query.append(" AND hostname IN (%s)" % self._get_comma_perc(cert_names)) + params.extend(n.lower() for n in cert_names) rows = self.query("".join(query), params, commit=True).fetchall() if len(rows)>1: @@ -860,6 +843,7 @@ class Server(ObjectReq): # These args are not for handler args.pop("client", None) args.pop("secret", None) + args.pop("hostnames", None) args = self.sanitize_args(path, method, args) result = method(**args) # call requested method @@ -1164,7 +1148,7 @@ def build_server(conf): section_def = { "log": ["FileLogger", "SysLogger"], "db": ["MySQL"], - "auth": ["X509Authenticator", "NoAuthenticator"], + "auth": ["X509Authenticator", "PlainAuthenticator"], "validator": ["JSONSchemaValidator", "NoValidator"], "handler": ["WardenHandler"], "server": ["Server"] @@ -1183,8 +1167,9 @@ def build_server(conf): "facility": {"type": facility, "default": "daemon"}, "level": {"type": loglevel, "default": "info"} }, - "NoAuthenticator": { - "req": {"type": obj, "default": "req"} + "PlainAuthenticator": { + "req": {"type": obj, "default": "req"}, + "db": {"type": obj, "default": "db"} }, "X509Authenticator": { "req": {"type": obj, "default": "req"}, -- GitLab