Repository: cloud-custodian/cloud-custodian

Issue:
Suggestion: switch c7n_mailer from Python-LDAP to ldap3
http://ldap3.readthedocs.io

For the sorts of read-only operations it’s doing, this would be a really simple change.

Advantages:

  * pure Python, so shipping it to Lambda (or even installing locally) would be greatly simplified
  * supports Python 3
  * a much better API (in my opinion)


Discussion:
interesting, thanks, that's definitely worth exploring even just on the basis of simplifying the install (strong preference to avoid checking in binaries into the repo).


Resolving patch:
diff --git a/tools/c7n_mailer/c7n_mailer/address.py b/tools/c7n_mailer/c7n_mailer/address.py
--- a/tools/c7n_mailer/c7n_mailer/address.py
+++ b/tools/c7n_mailer/c7n_mailer/address.py
@@ -11,14 +11,13 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
-# python ldap rhel 7.2 compile deps
-# yum install -y gcc openssl-devel openldap-devel
-# pip install python-ldap
-
-import ldap
 import logging
 import os
+from ldap3 import (
+    Connection,
+    Server,
+)
+from ldap3.core.exceptions import LDAPSocketOpenError
 
 
 log = logging.getLogger('custodian.ldap')
@@ -27,22 +26,22 @@
 
 
 def get_connection(ldap_uri, bind_user, bind_password):
-    conn = ldap.initialize(ldap_uri)
-    conn.set_option(ldap.OPT_REFERRALS, 0)
-    conn.set_option(ldap.OPT_NETWORK_TIMEOUT, 30)
-    conn.protocol_version = 3
-
     if not (bind_user and bind_password):
         if 'SSO_USER' in os.environ:
             bind_user = os.environ['SSO_USER']
             bind_password = os.environ['SSO_PASS']
         else:
             raise ValueError("missing ldap credentials")
+    server = Server(ldap_uri)
     try:
-        result = conn.simple_bind_s(bind_user, bind_password)
-    except ldap.SERVER_DOWN:
-        return None
-
+        conn = Connection(
+            server, user=bind_user, password=bind_password,
+            auto_bind=True,
+            receive_timeout=30,
+            auto_referrals=False,
+        )
+    except LDAPSocketOpenError:
+        conn = None
     return conn
 
 
@@ -68,21 +67,23 @@ def get_user(eid, bind_user=None, bind_password=None,
     if manager:
         attributes.append('manager')
 
-    query = 'sAMAccountName={0}'.format(eid)
-    ldap_result_id = CONN.search(
-        base_dn, ldap.SCOPE_SUBTREE, query, attributes)
-    ok, results = CONN.result(ldap_result_id, 0)
-    if ok != ldap.RES_SEARCH_ENTRY:
-        log.warning("userid not found %s" % (eid))
+    query = '(sAMAccountName={0})'.format(eid)
+    CONN.search(base_dn, query, attributes=attributes)
+    if len(CONN.entries) == 0:
+        log.warning("userid not found %s", eid)
+        return None
+    if len(CONN.entries) > 1:
+        log.warning("too many results for search %s", query)
         return None
 
-    cn, info = results[0]
-    info = {k: v[0] for k, v in info.items()}
+    entry = CONN.entries[0]
+    info = {attr.key: attr.value for attr in entry}
 
     if manager:
         manager_eid = info['manager'].split(',', 1)[0].split('=')[1]
         manager = get_user(manager_eid, manager=False)
-        info['manager_email'] = manager['mail']
-        info['manager_name'] = manager['displayName']
+        if manager is not None:
+            info['manager_email'] = manager['mail']
+            info['manager_name'] = manager['displayName']
         info.pop('manager')
     return info
diff --git a/tools/c7n_mailer/c7n_mailer/deploy.py b/tools/c7n_mailer/c7n_mailer/deploy.py
--- a/tools/c7n_mailer/c7n_mailer/deploy.py
+++ b/tools/c7n_mailer/c7n_mailer/deploy.py
@@ -36,9 +36,7 @@ def dispatch(event, context):
 def get_archive(config):
 
     required = ['ldap', 'jinja2', 'markupsafe']
-    remove = ['_ldap.so',
-              '_yaml.so',
-              'c7n.egg-link']
+    remove = ['_yaml.so', 'c7n.egg-link']
 
     def lib_filter(root, dirs, files):
         for f in tuple(files):
@@ -70,10 +68,6 @@ def lib_filter(root, dirs, files):
 
     archive.add_contents('config.json', json.dumps(config))
     archive.add_contents('periodic.py', entry_source)
-    archive.add_contents(
-        '_ldap.so',
-        open(os.path.join(
-            os.path.dirname(sys.executable), '..', 'deps', '_ldap.so')).read())
 
     archive.close()
     return archive
diff --git a/tools/c7n_mailer/setup.py b/tools/c7n_mailer/setup.py
--- a/tools/c7n_mailer/setup.py
+++ b/tools/c7n_mailer/setup.py
@@ -24,7 +24,7 @@
     ],
     url="https://github.com/capitalone/cloud-custodian",
     license="Apache-2.0",
-    find_packages('c7n_mailer'),
+    packages=find_packages('c7n_mailer'),
     entry_points={
         'console_scripts': [
             'c7n-mailer = c7n_mailer.cli:main']},


Test patch: