From 5d2a88b991ac9bf93462e6b17c6abebadda6c341 Mon Sep 17 00:00:00 2001 From: Brian Dolbec Date: Fri, 21 Jun 2013 18:46:22 -0700 Subject: [PATCH] Initial commit of an ldap search and seed file creation tool. This app is intended to be run on infra machines or a dev's home directory on dev.gentoo.org in order for it to have access to the gentoo ldap server. --- bin/ldap-seeds | 51 ++++++++++ gkeyldap/__init__.py | 1 + gkeyldap/cli.py | 215 +++++++++++++++++++++++++++++++++++++++++++ gkeyldap/search.py | 111 ++++++++++++++++++++++ gkeys/cli.py | 12 ++- testpath | 4 +- 6 files changed, 390 insertions(+), 4 deletions(-) create mode 100755 bin/ldap-seeds create mode 100644 gkeyldap/__init__.py create mode 100644 gkeyldap/cli.py create mode 100644 gkeyldap/search.py diff --git a/bin/ldap-seeds b/bin/ldap-seeds new file mode 100755 index 0000000..c40bd48 --- /dev/null +++ b/bin/ldap-seeds @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +'''Gentoo-keys is a gpg key manager for managing + gentoo's gpg-signing keys. It is these keys that are + used to verify and validate release media, etc.. + + Distributed under the terms of the GNU General Public License v2 + + Copyright: + (c) 2011 Brian Dolbec + Distributed under the terms of the GNU General Public License v2 + + Author(s): + Brian Dolbec + +''' + +from __future__ import print_function + +import os +import sys +# This block ensures that ^C interrupts are handled quietly. +try: + import signal + + def exithandler(signum,frame): + signal.signal(signal.SIGINT, signal.SIG_IGN) + signal.signal(signal.SIGTERM, signal.SIG_IGN) + print() + sys.exit(1) + + signal.signal(signal.SIGINT, exithandler) + signal.signal(signal.SIGTERM, exithandler) + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + +except KeyboardInterrupt: + print() + sys.exit(1) + + +from gkeyldap.cli import Main + +root = None +try: + root = os.environ['ROOT'] +except KeyError: + pass + +main = Main(root=root) +main() diff --git a/gkeyldap/__init__.py b/gkeyldap/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/gkeyldap/__init__.py @@ -0,0 +1 @@ + diff --git a/gkeyldap/cli.py b/gkeyldap/cli.py new file mode 100644 index 0000000..f3c2ced --- /dev/null +++ b/gkeyldap/cli.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python + +from __future__ import print_function + +import ldap +import sys +import os +import argparse + +from gkeys.log import logger +from gkeys.config import GKeysConfig, GKEY +from gkeys.seed import Seeds +from gkeyldap.search import (LdapSearch, UID, CN, STATUS, GPGKEY, + MAIL, GPGFINGERPRINT, gkey2ldap_map, gkey2SEARCH) + + +# set debug level to max +logger.setLevel(1) + + +class Main(object): + '''Main command line interface class''' + + + def __init__(self, root=None, config=None, print_results=True): + """ Main class init function. + + @param root: string, root path to use + """ + self.root = root or "/" + self.config = config or GKeysConfig(root=root) + self.print_results = print_results + self.args = None + self.seeds = None + + + def __call__(self, args=None): + logger.debug("Main:__call__()") + if args: + self.run(self.parse_args(args)) + else: + self.run(self.parse_args(sys.argv[1:])) + + + def parse_args(self, args): + '''Parse a list of aruments + + @param args: list + @returns argparse.Namespace object + ''' + logger.debug('args: %s' % args) + actions = ['ldapsearch', 'updateseeds'] + parser = argparse.ArgumentParser( + prog='gkeys', + description='Gentoo-keys manager program', + epilog='''Caution: adding untrusted keys to these keyrings can + be hazardous to your system!''') + # actions + parser.add_argument('action', choices=actions, nargs='?', + default='ldapsearch', help='Search ldap or update the seed file') + # options + parser.add_argument('-c', '--config', dest='config', default=None, + help='The path to an alternate config file') + parser.add_argument('-d', '--dest', dest='destination', default=None, + help='The destination db file path') + parser.add_argument('-N', '--name', dest='name', default=None, + help='The name to search for') + parser.add_argument('-n', '--nick', dest='nick', default=None, + help='The nick or user id (uid) to search for') + parser.add_argument('-m', '--mail', dest='mail', default=None, + help='The email address to search for') + parser.add_argument('-k', '--keyid', dest='keyid', default=None, + help='The gpg keyid to search for') + parser.add_argument('-f', '--fingerprint', dest='fingerprint', default=None, + help='The gpg fingerprint to search for') + parser.add_argument('-S', '--status', default=False, + help='The seedfile path to use') + + return parser.parse_args(args) + + + def run(self, args): + '''Run the args passed in + + @param args: list or argparse.Namespace object + ''' + if not args: + logger.error("Main.run() invalid args argument passed in") + if isinstance(args, list): + args = self.parse_args(args) + if args.config: + logger.debug("Found alternate config request: %s" % args.config) + self.config.defaults['config'] = args.config + # now make it load the config file + self.config.read_config() + + func = getattr(self, '_action_%s' % args.action) + logger.debug('Found action: %s' % args.action) + results = func(args) + + + def _action_ldapsearch(self, args): + l = LdapSearch() + if not l.connect(): + print("Aborting Search...Connection failed") + return False + logging.debug("args = %s" % str(args)) + x, target, search_field = self.get_args(args) + results = l.search(target, search_field) + devs = l.result2dict(results, gkey2ldap_map[x]) + for dev in sorted(devs): + print(dev, devs[dev]) + print("============================================") + print "Total number of devs in results:", len(devs) + return True + + + def _action_updateseeds(self, args): + l = LdapSearch() + if not l.connect(): + print("Aborting Update...Connection failed") + return False + results = l.search('*', UID) + info = l.result2dict(results, 'uid') + logger.debug("_action_updateseeds, got results :) converted to info") + if not self.create_seedfile(info): + logger.error("Dev seed file update failure: " + "Original seed file is intact & untouched.") + old = self.config['dev-seedfile'] + '.old' + try: + if os.path.exists(old): + logger.debug("Removing 'old' seed file: %s" % old) + os.unlink(old) + if os.path.exists(self.config['dev-seedfile']): + logger.debug("Renaming current seed file to: %s" % old) + os.rename(self.config['dev-seedfile'], old) + logger.debug("Renaming 'new' seed file to: %s" % self.config['dev-seedfile']) + os.rename(self.config['dev-seedfile'] + '.new', + self.config['dev-seedfile']) + except IOError: + raise + print("Developer Seed file updated") + return True + + + def create_seedfile(self, devs): + logger.debug("create_seedfile, arrived") + filename = self.config['dev-seedfile'] + '.new' + self.seeds = Seeds(filename) + for dev in devs: + logger.debug("create_seedfile, dev = %s, %s" % (str(dev), str(devs[dev]))) + new_gkey = GKEY._make(self.build_gkeylist(devs[dev])) + self.seeds.add(new_gkey) + logger.debug("create_seedfile, seeds created...saving file: %s" % filename) + return self.seeds.save() + + + @staticmethod + def get_args(args): + for x in ['nick', 'name', 'gpgkey', 'fingerprint', 'status']: + if x: + target = getattr(args, x) + search_field = gkey2SEARCH[x] + break + return (x, target, search_field) + + + + @staticmethod + def build_gkeydict(info): + keyinfo = {} + for x in GKEY._fields: + field = gkey2ldap_map[x] + if not field: + continue + try: + values = info[field] + if values and values in ['uid' ]: + value = values[0] + else: + value = values + if value: + keyinfo[x] = value + except KeyError: + pass + return keyinfo + + + @staticmethod + def build_gkeylist(info): + keyinfo = [] + logger.debug("build_gkeylist, info = %s" % str(info)) + for x in GKEY._fields: + field = gkey2ldap_map[x] + if not field: + keyinfo.append(None) + continue + try: + values = info[field] + if values and field in ['uid', 'name' ]: + value = values[0] + else: + value = values + keyinfo.append(value) + except KeyError: + keyinfo.append(None) + return keyinfo + + +if __name__ == '__main__': + + Main() + + + diff --git a/gkeyldap/search.py b/gkeyldap/search.py new file mode 100644 index 0000000..f432dcf --- /dev/null +++ b/gkeyldap/search.py @@ -0,0 +1,111 @@ +import ldap +import sys + +from gkeys.log import logger +from gkeys.config import GKEY + + +# set debug level to max +logger.setLevel(1) + + +default_server = 'ldap://ldap1.gentoo.org' +# add uid to the results so you don't have to +# separate it out of the results tuple[0] value +default_fields = ['uid', 'cn', 'mail', 'gentooStatus', 'gpgkey', 'gpgfingerprint'] +default_criteria = 'ou=devs,dc=gentoo,dc=org' + +# establish a ldap fields to GKEY._fields map +gkey2ldap_map = { + 'nick': 'uid', + 'name': 'cn', + 'keyid': 'gpgkey', + 'longkeyid': '', + 'keyring': '', + 'fingerprint': 'gpgfingerprint' +} +# Sanity check they are in sync +if not sorted(gkey2ldap_map) == sorted(GKEY._fields): + raise "Search.py out of sync with GKEY class" + + +# Now for some search field defaults +UID = '(uid=%s)' +CN = '(cn=%s)' +STATUS = '(gentooStatus=%s)' +GPGKEY = '(gpgkey=%s)' +MAIL = '(mail=%s)' +GPGFINGERPRINT = '(gpgfingerprint=%s)' + +gkey2SEARCH = { + 'nick': UID, + 'name': CN, + 'status': STATUS, + 'keyid': GPGKEY, + 'mail': MAIL, + 'fingerprint': GPGFINGERPRINT, +} + + +class LdapSearch(object): + '''Class to perform searches on the configured ldap server + ''' + + def __init__(self, server=None, fields=None, criteria=None): + self.server = server or default_server + self.fields = fields or default_fields + self.criteria = criteria or default_criteria + logger.debug('LdapSearch: __init__; server...: %s' % self.server) + logger.debug('LdapSearch: __init__; fields...: %s' % self.fields) + logger.debug('LdapSearch: __init__; criteria.: %s' % self.criteria) + self.ldap_connection = None + + + def connect(self, server=None,): + '''Creates our ldap server connection + + ''' + if server: + self.server = server + logger.debug('LdapSearch: connect; new server: %s' % self.server) + try: + self.ldap_connection = ldap.initialize(self.server) + self.ldap_connection.set_option(ldap.OPT_X_TLS_DEMAND, True) + self.ldap_connection.start_tls_s() + self.ldap_connection.simple_bind_s() + except Exception as e: + logger.error('LdapSearch: connect; failed to connect ot server: %s' % self.server) + logger.error("Exception was: %s" % str(e)) + return False + logger.debug('LdapSearch: connect; connection: %s' % self.ldap_connection) + return True + + + + def search(self, target, search_field=UID, fields=None, criteria=None): + '''Perform the ldap search + ''' + if not target: + logger.debug('LdapSearch: search; invalid target: "%s"' % target) + return {} + if not fields: + fields = self.fields + else: + logger.debug('LdapSearch: search; new fields: %s' % str(fields)) + if not criteria: + criteria = self.criteria + else: + logger.debug('LdapSearch: search; new criteria: %s' % criteria) + results = self.ldap_connection.search_s(criteria, + ldap.SCOPE_ONELEVEL, search_field % target, fields) + #logger.debug('LdapSearch: search; result = %s' % str(results)) + return results + + + def result2dict(self, results, key='uid'): + _dict = {} + for entry in results: + info = entry[1] + key_value = info[key][0] + _dict[key_value] = info + return _dict diff --git a/gkeys/cli.py b/gkeys/cli.py index f9720f9..133a14f 100644 --- a/gkeys/cli.py +++ b/gkeys/cli.py @@ -110,6 +110,10 @@ class Main(object): func = getattr(self, '_action_%s' % args.action) logger.debug('Found action: %s' % args.action) results = func(args) + if not results: + print("No results found. Check your configuration and that the", + "seed file exists.") + return # super simple output for the time being if self.print_results: print('\n\nGkey results:') @@ -142,6 +146,8 @@ class Main(object): def _load_seeds(self, filename): + if not filename: + return None filepath = self.config.get_key(filename + "-seedfile") logger.debug("_load_seeds(); seeds filepath to load: " "%s" % filepath) @@ -155,8 +161,10 @@ class Main(object): kwargs = self.build_gkeydict(args) logger.debug("_action_listseed(); kwargs: %s" % str(kwargs)) seeds = self._load_seeds(args.seeds) - results = seeds.list(**kwargs) - return results + if seeds: + results = seeds.list(**kwargs) + return results + return None def _action_addseed(self, args): diff --git a/testpath b/testpath index ed569d0..a9da312 100644 --- a/testpath +++ b/testpath @@ -12,6 +12,6 @@ # $ esearch some-package -export PATH="$(dirname $BASH_SOURCE[0])../pyGPG/bin:$(dirname $BASH_SOURCE[0])/bin:${PATH}" +export PATH="$(dirname $BASH_SOURCE[0])/../pyGPG/bin:$(dirname $BASH_SOURCE[0])/bin:${PATH}" -export PYTHONPATH="$(dirname $BASH_SOURCE[0])../pyGPG/:$(dirname $BASH_SOURCE[0])/:${PYTHONPATH}" +export PYTHONPATH="$(dirname $BASH_SOURCE[0])/../pyGPG/:$(dirname $BASH_SOURCE[0])/:${PYTHONPATH}" -- 2.26.2