From d7483f5d9ca8c44ce356283fed423c06f850bb4d Mon Sep 17 00:00:00 2001
From: Benjamin Graillot <graillot@crans.org>
Date: Sun, 9 Aug 2020 15:00:13 +0200
Subject: [PATCH] [ldap.py] LDAP lookup plugin

---
 lookup_plugins/ldap.py | 101 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 101 insertions(+)
 create mode 100644 lookup_plugins/ldap.py

diff --git a/lookup_plugins/ldap.py b/lookup_plugins/ldap.py
new file mode 100644
index 00000000..87cee458
--- /dev/null
+++ b/lookup_plugins/ldap.py
@@ -0,0 +1,101 @@
+import ipaddress
+
+from ansible.errors import AnsibleError, AnsibleParserError
+from ansible.plugins.lookup import LookupBase
+from ansible.utils.display import Display
+
+import ldap
+
+display = Display()
+
+def decode_object(object):
+    return {attribute: [value.decode('utf-8') for value in object[attribute]] for attribute in object}
+
+class LookupModule(LookupBase):
+
+    def __init__(self, **kwargs):
+        self.base = ldap.initialize('ldaps://localhost:1636/')
+        self.base.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)
+        self.base.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
+        self.base_dn = 'dc=crans,dc=org'
+
+    def query(self, base, scope, filter='(objectClass=*)', attr=None):
+        """
+        Make a LDAP query
+        query('ldap', 'query', BASE, SCOPE[, FILTER[, ATTR]])
+           BASE: base dn
+           SCOPE: 'base', 'one' or 'sub'
+           FILTER: ldap filter (optional)
+           ATTR: list of attributes (optional)
+        """
+        scope = { 'base': ldap.SCOPE_BASE, 'one': ldap.SCOPE_ONELEVEL, 'sub': ldap.SCOPE_SUBTREE }[scope]
+        query_id = self.base.search(f"{base}", scope, filter, attr)
+        result = self.base.result(query_id)[1]
+        result = { dn: decode_object(entry) for dn, entry in result }
+        return result
+
+    def ip(self, host, vlan):
+        """
+        Retrieve IP addresses of an interface of a device
+        query('ldap', 'ip', HOST, VLAN)
+        """
+        if isinstance(vlan, int):
+            network_query_id = self.base.search(f"ou=networks,{self.base_dn}", ldap.SCOPE_ONELEVEL, f"description={vlan}")
+            network_result = self.base.result(network_query_id)
+            vlan = network_result[1][0][1]['cn'][0].decode('utf-8')
+        if vlan == 'srv':
+            query_id = self.base.search(f"cn={host}.crans.org,cn={host},ou=hosts,{self.base_dn}", ldap.SCOPE_BASE)
+        else:
+            query_id = self.base.search(f"cn={host}.{vlan}.crans.org,cn={host},ou=hosts,{self.base_dn}", ldap.SCOPE_BASE)
+        result = self.base.result(query_id)
+        result = result[1][0][1]
+        result = [res.decode('utf-8') for res in result['ipHostNumber']]
+        return result
+
+    def run(self, terms, variables=None, **kwargs):
+        if terms[0] == 'query':
+            result = self.query(*terms[1:])
+        elif terms[0] == 'ip':
+            result = self.ip(*terms[1:])
+        elif terms[0] == 'group':
+            query_id = self.base.search(f"ou=group,{self.base_dn}", ldap.SCOPE_SUBTREE, "objectClass=posixGroup")
+            result = self.base.result(query_id)
+            result = result[1]
+        # query interface attribute
+        # query('ldap', 'hosts', HOST, VLAN, ATTR)
+        #   HOST: device name
+        #   VLAN: vlan name
+        #   ATTR: attribute
+        elif terms[0] == 'hosts':
+            host = terms[1]
+            vlan = terms[2]
+            attr = terms[3]
+            if isinstance(vlan, int):
+                network_query_id = self.base.search(f"ou=networks,{self.base_dn}", ldap.SCOPE_ONELEVEL, f"description={vlan}")
+                network_result = self.base.result(network_query_id)
+                vlan = network_result[1][0][1]['cn'][0].decode('utf-8')
+            if vlan == 'srv':
+                query_id = self.base.search(f"cn={host}.crans.org,cn={host},ou=hosts,{self.base_dn}", ldap.SCOPE_BASE)
+            else:
+                query_id = self.base.search(f"cn={host}.{vlan}.crans.org,cn={host},ou=hosts,{self.base_dn}", ldap.SCOPE_BASE)
+            result = self.base.result(query_id)
+            result = result[1][0][1]
+            result = [res.decode('utf-8') for res in result[attr]]
+        elif terms[0] == 'networks':
+            network = terms[1]
+            query_id = self.base.search(f"cn={network},ou=networks,{self.base_dn}", ldap.SCOPE_BASE, "objectClass=ipNetwork")
+            result = self.base.result(query_id)
+            result = result[1][0][1]
+            return [str(ipaddress.ip_network('{}/{}'.format(result['ipNetworkNumber'][0].decode('utf-8'), result['ipNetmaskNumber'][0].decode('utf-8'))))]
+        elif terms[0] == 'vlanid':
+            network = terms[1]
+            query_id = self.base.search(f"cn={network},ou=networks,{self.base_dn}", ldap.SCOPE_BASE, "objectClass=ipNetwork")
+            result = self.base.result(query_id)
+            result = result[1][0][1]
+            return int(result['description'][0])
+        elif terms[0] == 'role':
+            role = terms[1]
+            query_id = self.base.search(f"ou=hosts,{self.base_dn}", ldap.SCOPE_ONELEVEL, f"description={role}")
+            result = self.base.result(query_id)
+            result = [cn.decode('utf-8') for res in result[1] for cn in res[1]['cn']]
+        return result
-- 
GitLab