Skip to content
Snippets Groups Projects
Commit 308e3f6f authored by Pavel Kácha's avatar Pavel Kácha
Browse files

PlainAuthenticator (forego X509 for testing) (thx to Radko Krkos)

parent 92c7487c
No related branches found
No related tags found
No related merge requests found
...@@ -37,7 +37,7 @@ B. Dependencies ...@@ -37,7 +37,7 @@ B. Dependencies
1. Platform 1. Platform
Python 2.7+ Python 2.7+
Apache 2.2 Apache 2.2/2.4
mod_wsgi 3.3+ mod_wsgi 3.3+
2. Python modules 2. Python modules
...@@ -123,7 +123,7 @@ particular implementation object of the aspect, for example type of logger ...@@ -123,7 +123,7 @@ particular implementation object of the aspect, for example type of logger
Log: FileLogger, SysLogger Log: FileLogger, SysLogger
DB: MySQL DB: MySQL
Auth: X509Authenticator, NoAuthenticator Auth: X509Authenticator, PlainAuthenticator
Validator: JSONSchemaValidator, NoValidator Validator: JSONSchemaValidator, NoValidator
Handler: WardenHandler Handler: WardenHandler
...@@ -142,12 +142,15 @@ object from particular section list is used ("FileLogger" for example). ...@@ -142,12 +142,15 @@ object from particular section list is used ("FileLogger" for example).
facility: syslog facility, defaults to "daemon" facility: syslog facility, defaults to "daemon"
level: least log level (CRITICAL, ERROR, WARNING, INFO, DEBUG) level: least log level (CRITICAL, ERROR, WARNING, INFO, DEBUG)
NoAuthenticator: forego authentication, for debug purposes
X509Authenticator: authenticate based on certificate chain validation, X509Authenticator: authenticate based on certificate chain validation,
hostname corresponding with certificate CN or SubjectAltName and hostname corresponding with certificate CN or SubjectAltName and
optionally shared secret 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 NoValidator: forego event JSON validation, for debug purposes
JSONSchemaValidator: validate incoming events based on JSON schema file JSONSchemaValidator: validate incoming events based on JSON schema file
......
...@@ -272,44 +272,63 @@ class ObjectReq(Object): ...@@ -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) ObjectReq.__init__(self, req)
self.db = db
def shash(self, s): def __str__(self):
""" Simple FNV1 hash for creating ids on the fly """ return "%s(req=%s, db=%s)" % (type(self).__name__, type(self.req).__name__, type(self.db).__name__)
res = 2166136261
for c in s:
res = 0xFFFFFFFF & res * 16777619 ^ ord(c)
return res
def authenticate(self, env, args): def authenticate(self, env, args):
name = args.get("client", [None])[0] name = args.get("client", [None])[0]
if name is None: secret = args.get("secret", [None])[0]
logging.error("NoAuthenticator: clients must authenticate by name, not secret") 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 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
return (client is not None)
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): if method.write:
NoAuthenticator.__init__(self, req) if not (client.write or client.test):
self.db = db 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): def get_cert_dns_names(self, pem):
...@@ -330,7 +349,7 @@ class X509Authenticator(NoAuthenticator): ...@@ -330,7 +349,7 @@ class X509Authenticator(NoAuthenticator):
return [firstcommon] + list(set(altnames+commons) - set([firstcommon])) return [firstcommon] + list(set(altnames+commons) - set([firstcommon]))
def authenticate (self, env, args): def authenticate(self, env, args):
try: try:
cert_names = self.get_cert_dns_names(env["SSL_CLIENT_CERT"]) cert_names = self.get_cert_dns_names(env["SSL_CLIENT_CERT"])
except: except:
...@@ -338,46 +357,9 @@ class X509Authenticator(NoAuthenticator): ...@@ -338,46 +357,9 @@ class X509Authenticator(NoAuthenticator):
exception.log(logging.getLogger()) exception.log(logging.getLogger())
return None return None
name = args.get("client", [None])[0] args["hostnames"] = [cert_names]
secret = args.get("secret", [None])[0]
client = self.db.get_client_by_name(cert_names, name, secret) return PlainAuthenticator.authenticate(self, env, args)
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
class NoValidator(ObjectReq): class NoValidator(ObjectReq):
...@@ -510,7 +492,7 @@ class MySQL(ObjectReq): ...@@ -510,7 +492,7 @@ class MySQL(ObjectReq):
return "" if b else "NOT" 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"] query = ["SELECT * FROM clients WHERE valid = 1"]
params = [] params = []
if name: if name:
...@@ -519,8 +501,9 @@ class MySQL(ObjectReq): ...@@ -519,8 +501,9 @@ class MySQL(ObjectReq):
if secret: if secret:
query.append(" AND secret = %s") query.append(" AND secret = %s")
params.append(secret) params.append(secret)
query.append(" AND hostname IN (%s)" % self._get_comma_perc(cert_names)) if cert_names:
params.extend(n.lower() for n in 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() rows = self.query("".join(query), params, commit=True).fetchall()
if len(rows)>1: if len(rows)>1:
...@@ -860,6 +843,7 @@ class Server(ObjectReq): ...@@ -860,6 +843,7 @@ class Server(ObjectReq):
# These args are not for handler # These args are not for handler
args.pop("client", None) args.pop("client", None)
args.pop("secret", None) args.pop("secret", None)
args.pop("hostnames", None)
args = self.sanitize_args(path, method, args) args = self.sanitize_args(path, method, args)
result = method(**args) # call requested method result = method(**args) # call requested method
...@@ -1164,7 +1148,7 @@ def build_server(conf): ...@@ -1164,7 +1148,7 @@ def build_server(conf):
section_def = { section_def = {
"log": ["FileLogger", "SysLogger"], "log": ["FileLogger", "SysLogger"],
"db": ["MySQL"], "db": ["MySQL"],
"auth": ["X509Authenticator", "NoAuthenticator"], "auth": ["X509Authenticator", "PlainAuthenticator"],
"validator": ["JSONSchemaValidator", "NoValidator"], "validator": ["JSONSchemaValidator", "NoValidator"],
"handler": ["WardenHandler"], "handler": ["WardenHandler"],
"server": ["Server"] "server": ["Server"]
...@@ -1183,8 +1167,9 @@ def build_server(conf): ...@@ -1183,8 +1167,9 @@ def build_server(conf):
"facility": {"type": facility, "default": "daemon"}, "facility": {"type": facility, "default": "daemon"},
"level": {"type": loglevel, "default": "info"} "level": {"type": loglevel, "default": "info"}
}, },
"NoAuthenticator": { "PlainAuthenticator": {
"req": {"type": obj, "default": "req"} "req": {"type": obj, "default": "req"},
"db": {"type": obj, "default": "db"}
}, },
"X509Authenticator": { "X509Authenticator": {
"req": {"type": obj, "default": "req"}, "req": {"type": obj, "default": "req"},
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment