Initial commit of an ldap search and seed file creation tool.
authorBrian Dolbec <dolsen@gentoo.org>
Sat, 22 Jun 2013 01:46:22 +0000 (18:46 -0700)
committerBrian Dolbec <dolsen@gentoo.org>
Sat, 22 Jun 2013 23:00:47 +0000 (16:00 -0700)
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 [new file with mode: 0755]
gkeyldap/__init__.py [new file with mode: 0644]
gkeyldap/cli.py [new file with mode: 0644]
gkeyldap/search.py [new file with mode: 0644]
gkeys/cli.py
testpath

diff --git a/bin/ldap-seeds b/bin/ldap-seeds
new file mode 100755 (executable)
index 0000000..c40bd48
--- /dev/null
@@ -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 <dolsen@gentoo.org>
+
+'''
+
+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 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/gkeyldap/cli.py b/gkeyldap/cli.py
new file mode 100644 (file)
index 0000000..f3c2ced
--- /dev/null
@@ -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 (file)
index 0000000..f432dcf
--- /dev/null
@@ -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
index f9720f9e5a8c85d418394be4334b927435ba6d03..133a14f92cc3c9a318ab4e7d3382ea0db79c22de 100644 (file)
@@ -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):
index ed569d02a3f367d48516fbc17d28ad4c33834b64..a9da3122acefa84b38943536914033aaf8cc9e9d 100644 (file)
--- 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}"