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

Added command line client management

parent e7501fc0
No related branches found
No related tags found
No related merge requests found
...@@ -14,6 +14,8 @@ import M2Crypto.X509 ...@@ -14,6 +14,8 @@ import M2Crypto.X509
import json import json
import MySQLdb as my import MySQLdb as my
import MySQLdb.cursors as mycursors import MySQLdb.cursors as mycursors
import re
import email.utils
from collections import namedtuple from collections import namedtuple
from uuid import uuid4 from uuid import uuid4
from time import time, gmtime, sleep from time import time, gmtime, sleep
...@@ -210,17 +212,17 @@ def SysLogger(req, socket="/dev/log", facility=logging.handlers.SysLogHandler.LO ...@@ -210,17 +212,17 @@ def SysLogger(req, socket="/dev/log", facility=logging.handlers.SysLogHandler.LO
class Client(namedtuple("ClientTuple", class Client(namedtuple("ClientTuple",
["id", "registered", "requestor", "hostname", "service", "note", ["id", "registered", "requestor", "hostname", "service", "note",
"identity", "secret", "read", "debug", "write", "test"])): "valid", "identity", "secret", "read", "debug", "write", "test"])):
def __str__(self): def __str__(self):
return ( return (
"%s(id=%i, registered=%s, requestor=\"%s\", hostname=\"%s\", " "%s(id=%i, registered=%s, requestor=\"%s\", hostname=\"%s\", "
"service=\"%s\", note=\"%s\", identity=\"%s\", secret=%s, " "service=\"%s\", note=\"%s\", identity=\"%s\", secret=%s, "
"read=%i, debug=%i, write=%i, test=%i)") % ( "valid=%i read=%i, debug=%i, write=%i, test=%i)") % (
type(self).__name__, self.id, self.registered, type(self).__name__, self.id, self.registered,
self.requestor, self.hostname, self.service, self.note, self.requestor, self.hostname, self.service, self.note,
self.identity, "..." if self.secret is not None else "None", self.identity, "..." if self.secret is not None else "None",
self.read, self.debug, self.write, self.test) self.valid, self.read, self.debug, self.write, self.test)
...@@ -514,7 +516,7 @@ class MySQL(ObjectReq): ...@@ -514,7 +516,7 @@ class MySQL(ObjectReq):
def get_client_by_name(self, cert_names, identity=None, secret=None): def get_client_by_name(self, cert_names, identity=None, secret=None):
query = ["SELECT id, registered, requestor, hostname, service, note, identity, secret, `read`, debug, `write`, test FROM clients WHERE valid = 1"] query = ["SELECT id, registered, requestor, hostname, service, note, valid, identity, secret, `read`, debug, `write`, test FROM clients WHERE valid = 1"]
params = [] params = []
if identity: if identity:
query.append(" AND identity = %s") query.append(" AND identity = %s")
...@@ -534,6 +536,42 @@ class MySQL(ObjectReq): ...@@ -534,6 +536,42 @@ class MySQL(ObjectReq):
return Client(**rows[0]) if rows else None return Client(**rows[0]) if rows else None
def get_clients(self, id=None):
query = ["SELECT id, registered, requestor, hostname, service, note, valid, identity, secret, `read`, debug, `write`, test FROM clients"]
params = []
if id:
query.append("WHERE id = %s")
params.append(id)
query.append("ORDER BY id")
rows = self.query(" ".join(query), params)
return [Client(**row) for row in rows]
def add_modify_client(self, id=None, **kwargs):
query = []
params = []
uquery = []
if id is None:
query.append("INSERT INTO clients SET")
uquery.append("registered = now()")
else:
query.append("UPDATE clients SET")
for attr in ["identity", "hostname", "requestor", "secret", "note",
"valid", "read", "write", "debug", "test"]:
val = kwargs.get(attr, None)
if val is not None:
uquery.append("`%s` = %%s" % attr)
params.append(val)
if not uquery:
return id
query.append(", ".join(uquery))
if id is not None:
query.append("WHERE id = %s")
params.append(id)
self.query(" ".join(query), params)
return self.crs.lastrowid if id is None else id
def get_debug(self): def get_debug(self):
rows = self.query("SELECT VERSION() AS VER") rows = self.query("SELECT VERSION() AS VER")
tablestat = self.query("SHOW TABLE STATUS") tablestat = self.query("SHOW TABLE STATUS")
...@@ -653,11 +691,13 @@ class MySQL(ObjectReq): ...@@ -653,11 +691,13 @@ class MySQL(ObjectReq):
self.query("INSERT INTO last_events(client_id, event_id, timestamp) VALUES(%s, %s, NOW())", (client.id, id), dml=True) self.query("INSERT INTO last_events(client_id, event_id, timestamp) VALUES(%s, %s, NOW())", (client.id, id), dml=True)
self.con.commit() self.con.commit()
def getLastEventId(self): def getLastEventId(self):
row = self.query("SELECT MAX(id) as id FROM events")[0] row = self.query("SELECT MAX(id) as id FROM events")[0]
return row['id'] or 0 return row['id'] or 0
def getLastReceivedId(self, client): def getLastReceivedId(self, client):
row = self.query("SELECT MAX(event_id) as id FROM last_events WHERE client_id = %s", client.id)[0] row = self.query("SELECT MAX(event_id) as id FROM last_events WHERE client_id = %s", client.id)[0]
...@@ -1163,11 +1203,191 @@ def build_server(conf): ...@@ -1163,11 +1203,191 @@ def build_server(conf):
logging.debug("", exc_info=sys.exc_info()) logging.debug("", exc_info=sys.exc_info())
return fallback_wsgi return fallback_wsgi
logging.info("Ready to serve") logging.info("Server ready")
return objects["server"] return objects["server"]
# Command line utilities
def check_config():
# If we got so far, server object got set up fine
print >>sys.stderr, "Looks clear."
return 0
def list_clients(id=None):
clients = server.handler.db.get_clients(id)
order = ["id", "registered", "requestor", "hostname", "service", "identity",
"secret", "valid", "read", "debug", "write", "test", "note"]
lines = [[str(getattr(client, col)) for col in order] for client in clients]
col_width = [max(len(val) for val in col) for col in zip(*(lines+[order]))]
divider = ["-" * l for l in col_width]
for line in [order, divider] + lines:
print " ".join([val.ljust(width) for val, width in zip(line, col_width)])
def register_client(name, hostname, requestor, secret, note, valid, read, write, debug, test):
# argparse does _always_ return something, so we cannot rely on missing arguments
if valid is None: valid = 1
if read is None: read = 1
if write is None: write = 0
if debug is None: debug = 0
if test is None: test = 1
modify_client(id=None,
name=name, hostname=hostname, requestor=requestor, secret=secret,
note=note, valid=valid, read=read, write=write, debug=debug, test=test)
def modify_client(id, name, hostname, requestor, secret, note, valid, read, write, debug, test):
def isValidHostname(hostname):
if len(hostname) > 255:
return False
if hostname.endswith("."): # A single trailing dot is legal
hostname = hostname[:-1] # strip exactly one dot from the right, if present
disallowed = re.compile("[^A-Z\d-]", re.IGNORECASE)
return all( # Split by labels and verify individually
(label and len(label) <= 63 # length is within proper range
and not label.startswith("-") and not label.endswith("-") # no bordering hyphens
and not disallowed.search(label)) # contains only legal characters
for label in hostname.split("."))
def isValidNSID(nsid):
allowed = re.compile("^(?:[a-zA-Z_][a-zA-Z0-9_]*\\.)*[a-zA-Z_][a-zA-Z0-9_]*$")
return allowed.match(nsid)
def isValidEmail(mail):
split = email.utils.parseaddr(mail)
allowed = re.compile("^[a-zA-Z0-9_.%!+-]+@[a-zA-Z0-9-.]+$") # just basic check
return allowed.match(split[1])
def isValidID(id):
client = server.handler.db.get_clients(id)
return client and True or False
if name is not None and not isValidNSID(name):
print >>sys.stderr, "Invalid client name \"%s\"." % name
if hostname is not None and not isValidHostname(hostname):
print >>sys.stderr, "Invalid hostname \"%s\"." % hostname
return 254
if requestor is not None and not isValidEmail(requestor):
print >>sys.stderr, "Invalid requestor email \"%s\"." % requestor
return 254
if id is not None and not isValidID(id):
print >>sys.stderr, "Invalid id \"%s\"." % id
return 254
existing_clients = server.handler.db.get_client_by_name([hostname], identity=name, secret=secret)
if existing_clients:
print >>sys.stderr, "Clash with existing hostname/identity/secret: %s" % str(existing_clients)
return 254
newid = server.handler.db.add_modify_client(
id=id, identity=name, hostname=hostname,
requestor=requestor, secret=secret, note=note, valid=valid,
read=read, write=write, debug=debug, test=test)
list_clients(id=newid)
def add_client_args(subargp, mod=False):
subargp.add_argument("--help", action="help", help="show this help message and exit")
if mod:
subargp.add_argument("-i", "--id", required=True, type=int,
help="client id")
subargp.add_argument("-n", "--name", required=not mod,
help="client name (in dotted reverse path notation)")
subargp.add_argument("-h", "--hostname", required=not mod,
help="client FQDN hostname")
subargp.add_argument("-r", "--requestor", required=not mod,
help="requestor email")
subargp.add_argument("-s", "--secret",
help="authentication token")
subargp.add_argument("--note",
help="client freetext description")
reg_valid = subargp.add_mutually_exclusive_group(required=False)
reg_valid.add_argument("--valid", action="store_const", const=1, default=None,
help="valid client (default)")
reg_valid.add_argument("--novalid", action="store_const", const=0, dest="valid", default=None)
reg_read = subargp.add_mutually_exclusive_group(required=False)
reg_read.add_argument("--read", action="store_const", const=1, default=None,
help="client is allowed to read (default)")
reg_read.add_argument("--noread", action="store_const", const=0, dest="read", default=None)
reg_write = subargp.add_mutually_exclusive_group(required=False)
reg_write.add_argument("--nowrite", action="store_const", const=0, dest="write", default=None,
help="client is allowed to send (default - no)")
reg_write.add_argument("--write", action="store_const", const=1, default=None)
reg_debug = subargp.add_mutually_exclusive_group(required=False)
reg_debug.add_argument("--nodebug", action="store_const", const=0, dest="debug", default=None,
help="client is allowed receive debug output (default - no)")
reg_debug.add_argument("--debug", action="store_const", const=1, default=None)
reg_test = subargp.add_mutually_exclusive_group(required=False)
reg_test.add_argument("--test", action="store_const", const=1, default=None,
help="client is yet in testing phase (default - yes)")
reg_test.add_argument("--notest", action="store_const", const=0, dest="test", default=None)
def get_args():
import argparse
argp = argparse.ArgumentParser(
description="Warden server " + VERSION, add_help=False)
argp.add_argument("--help", action="help",
help="show this help message and exit")
argp.add_argument("-c", "--config",
help="show this help message and exit")
subargp = argp.add_subparsers(title="commands")
subargp_check = subargp.add_parser("check", add_help=False,
description="Try to setup server based on configuration file.",
help="check configuration")
subargp_check.set_defaults(command=check_config)
subargp_check.add_argument("--help", action="help",
help="show this help message and exit")
subargp_reg = subargp.add_parser("register", add_help=False,
description="Add new client registration entry.",
help="register new client")
subargp_reg.set_defaults(command=register_client)
add_client_args(subargp_reg)
subargp_mod = subargp.add_parser("modify", add_help=False,
description="Modify details of client registration entry.",
help="modify client registration")
subargp_mod.set_defaults(command=modify_client)
add_client_args(subargp_mod, mod=True)
subargp_list = subargp.add_parser("list", add_help=False,
description="List details of client registration entries.",
help="list registered clients")
subargp_list.set_defaults(command=list_clients)
subargp_list.add_argument("--help", action="help",
help="show this help message and exit")
subargp_list.add_argument("--id", action="store", type=int,
help="client id", default=None)
return argp.parse_args()
if __name__=="__main__": if __name__=="__main__":
# FIXME: just development stuff args = get_args()
srv = build_server(read_ini("warden3.cfg.wheezy-warden3")) config = path.join(path.dirname(__file__), args.config or "warden_server.cfg")
server = build_server(read_cfg(config))
command = args.command
subargs = vars(args)
del subargs["command"]
del subargs["config"]
if not server or server is fallback_wsgi:
print >>sys.stderr, "Failed initialization, check configured log targets for reasons."
sys.exit(255)
sys.exit(command(**subargs))
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