diff --git a/README.md b/README.md index 0382fcf..1a967f2 100644 --- a/README.md +++ b/README.md @@ -101,4 +101,9 @@ The following locations are supported: curl -u nobody:SIGNAL_API_PASS -X POST -H "Content-Type: application/json" --data '{"message":"hallo world"}' localhost:5000/send-all +# Additional Packages Required + +The following additional packages might be requried (on Debian) to successfully install the `python-ldap`-requirement: + + apt install libsasl2-dev python-dev libldap2-dev libssl-dev diff --git a/interface.py b/interface.py index dc324d7..7bacf1e 100755 --- a/interface.py +++ b/interface.py @@ -86,6 +86,38 @@ def sendToAllIcinga(): sendMessageToAllClients(message) return ("","204") +@app.route('/smart-send', methods=["POST"]) +@login_required +def smart_send_to_clients(): + '''Send to clients based on querying the LDAP + requests MAY include: + - list of usernames under key "users" + - list of groups under key "groups" + - neither of the above to automatically target the configured administrators group" + retuest MUST include: + - message as STRING in field "msg" + OR + - supported struct of type "ICINGA|ZABBIX|GENERIC" (see docs) in field "data" + ''' + + instructions = flask.request.json + + users = instructions.get("users") + groups = instructions.get("groups") + message = instructions.get("msg") + + struct = instructions.get("data") + if struct: + try: + message = messagetools.load_struct(struct) + except messagetools.UnsupporedStruct() as e: + return (408, e.response()) + + + persons = ldaptools.select_targets(users, groups, app.config["LDAP_ARGS"]) + signal.bulk_dispatch(persons, message) + return (200, "OK") + @app.before_first_request def init(): app.config["PASSWORD"] = os.environ["SIGNAL_API_PASS"] @@ -99,8 +131,28 @@ if __name__ == "__main__": parser.add_argument('--port', default="5000", help='Port on which to listen') parser.add_argument("--signal-cli-bin", default=None, type=str, help="Path to signal-cli binary if no in $PATH") + + + parser.add_argument('--ldap-server') + parser.add_argument('--ldap-base-dn') + parser.add_argument('--ldap-manager-dn') + parser.add_argument('--ldap-manager-password') + args = parser.parse_args() + # define ldap args # + ldap_args = { + "LDAP_SERVER" : args.ldap_server, + "LDAP_BIND_DN" : args.manager_dn, + "LDAP_BIND_PW" : args.manager_password, + "LDAP_BASE_DN" : args.ldap_base_dn, + } + + if not any([value is None for value in ldap_args.values()]): + app.config["LDAP_ARGS"] = ldap_args + else: + app.config["LDAP_ARGS"] = None + app.config["SIGNAL_CLI_BIN"] = os.path.expanduser(args.signal_cli_bin) app.config["PASSWORD"] = os.environ["SIGNAL_API_PASS"] diff --git a/ldaptools.py b/ldaptools.py new file mode 100644 index 0000000..76a5076 --- /dev/null +++ b/ldaptools.py @@ -0,0 +1,111 @@ +import ldap + +# LDAP server details +ldap_server = "ldap://localhost:5005" +base_dn = "ou=People,dc=atlantishq,dc=de" +manager_dn = "cn=Manager,dc=atlantishq,dc=de" +manager_password = "flanigan" + +class Person: + + def __init__(self, cn, username, name, email, phone): + + self.cn = cn + self.username = username + self.name = name + self.email = email + self.pohon = phone + +def ldap_query(search_filter, ldap_args, alt_base_dn=None): + + ldap_server = ldap_args["LDAP_SERVER"] + manager_dn = ldap_args["LDAP_BIND_DN"] + manager_pw = ldap_args["LDAP_BIND_PW"] + base_dn = ldap_args["LDAP_BASE_DN"] + + # for example a specific user dn # + if alt_base_dn: + base_dn = alt_base_dn + + # estabilish connection + conn = ldap.initialize(ldap_server) + conn.simple_bind_s(manager_dn, manager_password) + + # search in scope # + search_scope = ldap.SCOPE_SUBTREE + search_results = conn.search_s(base_dn, search_scope, search_filter) + + # unbind from connection and return # + conn.unbind_s() + return search_results + +def _person_from_search_result(cn, entry): + + username = entry.get("uid", [None])[0] + name = entry..get("firstName", [None])[0] + email = entry.get("email", [None])[0] + phone = entry.get("telephoneNumber", [None])[0] + + return Person(cn, username, name, email, phone) + +def get_user_by_uid(username): + + if not username: + return None + + search_filter = "(&(objectClass=inetOrgPerson)(uid={username}))".format(username) + results = ldap_query(search_filter, ldap_args) + + if not results or len(results) < 1: + return None + + cn, p = results[0] + return _person_from_search_result(cn, p) + + +def get_members_of_group(group, ldap_args): + + if not group: + return [] + + search_filter = "(&(objectClass=groupOfNames)(cn={group_name})".format(group) + results = ldap_query(search_filter, ldap_args) + + if not results: + return [] + + group_dn, entry = results[0] + members = entry.get("member", []) + + persons = [] + for member in members: + + user_dn = member.decode("utf-8") + user_filter = "(objectClass=inetOrgPerson)" + results = ldap_query(user_filter, ldap_args, alt_base_dn=user_dn) + + if not results: + continue + + cn, entry = results[0] + person_obj = _person_from_search_result(cn, entry) + persons.append(person_obj) + + return persons + + +def select_targets(users, groups, ldap_args, admin_group="pki"): + '''Returns a list of persons to send notifications to''' + + persons = [] + if users: + for username in users: + persons.append(get_user_by_uid(username)) + elif groups: + for group in groups: + persons.append(get_members_of_group(group)) + else: + # send to administrators # + persons.append(get_members_of_group()) + + return persons