Changeset - e4a207fa3a07
[Not reviewed]
0 1 0
Branko Majic (branko) - 9 years ago 2014-12-16 00:18:23
branko@majic.rs
MAR-1: Added support for specifying LDAP UIR, bind DN, and bind password. Expanded examples for this. Refactored module to be a bit more robust/better encapsulated.
1 file changed with 143 insertions and 37 deletions:
0 comments (0 inline, 0 general)
roles/ldap_server/library/ldap_permissions
Show inline comments
 
@@ -32,6 +32,22 @@ options:
 
        format for specifying this parameter (see examples below).
 
    required: true
 
    default: ""
 
  server_uri:
 
    description:
 
      - LDAP connection URI specifying what server to connect to.
 
    required: false
 
    default: "ldapi:///"
 
  bind_dn:
 
    description:
 
      - DN for binding to the LDAP server using simple bind. If not set,
 
        EXTERNAL SASL binding method will be used.
 
    required: false
 
    default: ""
 
  bind_password:
 
    description:
 
      - Password for binding to the LDAP server using simple bind.
 
    required: false
 
    default: ""
 
"""
 

	
 
EXAMPLES = """
 
@@ -63,6 +79,31 @@ ldap_permissions:
 
  - filter: '(olcDatabase={0}config)'
 
    rules:
 
      - to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage by * break
 
# Set-up rules on a remote server.
 
ldap_permissions:
 
  - filter: '(olcSuffix=dc=example,dc=com)'
 
    rules:
 
      - >
 
        to *
 
        by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
 
        by * break
 
      - >
 
        to attrs=userPassword,shadowLastChange
 
        by self write
 
        by anonymous auth
 
        by dn="cn=admin,dc=example,dc=com" write
 
        by * none
 
      - >
 
        to dn.base=""
 
        by * read
 
      - >
 
        to *
 
        by self write
 
        by dn="cn=admin,dc=example,dc=com" write
 
        by * none
 
    server_uri: ldap://ldap.example.com
 
    bind_dn: cn=admin,dc=example,dc=com
 
    bind_password: somepassword
 
"""
 

	
 
# Try to load the Python LDAP module.
 
@@ -76,35 +117,74 @@ else:
 
    ldap_found = True
 

	
 

	
 
class LDAPPermissions(object):
 
def get_ldap_connection(uri, bind_dn=None, bind_password=""):
 
    """
 
    This class encapsulates functionality for applying ACL to an LDAP database.
 
    Connects and binds to an LDAP server.
 

	
 
    Arguments:
 

	
 
    uri
 
      LDAP connection URI specifying what server to connect to, including the
 
      protocol.
 

	
 
    bind_dn
 
      Distinguished name to be used for simple bind. If not set, SASL EXTERNAL
 
      mechanism will be used for log-in. Default is None.
 

	
 
    bind_password
 
      Password to be used for simple bind. Needs to be set only if bind_dn is
 
      set as well. Default is "".
 

	
 
    Returns:
 

	
 
    LDAP connection object.
 
    """
 

	
 
    def __init__(self, module):
 
        """
 
        Initialises class instance. Reads parameters from the passed
 
        AnsibleModule instance, and connects to the LDAP server.
 
        """
 
    connection = ldap.initialize(uri)
 

	
 
        self.module = module
 
        self.filter = module.params["filter"]
 
        self.rules = module.params["rules"]
 
        self._connect()
 
    if bind_dn:
 
        connection.simple_bind_s(bind_dn, bind_password)
 
    else:
 
        connection.sasl_interactive_bind_s("", ldap.sasl.external())
 

	
 
    def _connect(self):
 
    return connection
 

	
 

	
 
class DatabaseFilteringError(Exception):
 
    """
 
    Exception intended to be thrown in case the filter passed in to module did
 
    not match one and only one entry in the configuration database.
 
    """
 
    pass
 

	
 

	
 
class LDAPPermissions(object):
 
    """
 
    Implements a convenience wrapper for managing permissions in OpenLDAP
 
    server.
 
    """
 

	
 
    def __init__(self, ldap_filter, rules, connection):
 
        """
 
        Initialises LDAP connection and binds to the LDAP server.
 
        Initialises class instance, setting-up the necessary properties.
 

	
 
        Binding is done using the SASL EXTERNAL mechanism.
 
        Arguments:
 

	
 
        Returns:
 
        ldap_filter
 
          Filter that should be used under base cn=config to locate the database
 
          that should be modified.
 

	
 
        Nothing.
 
        rules
 
          Rules to apply.
 

	
 
        connection
 
          LDAP connection object instance. This connection will be used for
 
          running queries against an LDAP server.
 
        """
 

	
 
        self.connection = ldap.initialize("ldapi:///")
 
        self.connection.sasl_interactive_bind_s("", ldap.sasl.external())
 
        self.ldap_filter = ldap_filter
 
        self.rules = rules
 
        self.connection = connection
 

	
 
    def _get_database(self):
 
        """
 
@@ -115,11 +195,13 @@ class LDAPPermissions(object):
 
        Database entry. Return format is same as for function ldap.search_s.
 
        """
 

	
 
        return self.connection.search_s(base="cn=config", scope=ldap.SCOPE_ONELEVEL, filterstr=self.filter)
 
        return self.connection.search_s(base="cn=config",
 
                                        scope=ldap.SCOPE_ONELEVEL,
 
                                        filterstr=self.ldap_filter)
 

	
 
    def _get_modifications(self, database):
 
        """
 
        Returns modification list for updatingn the current ACL with requested
 
        Returns modification list for updating the current ACL with requested
 
        ACL.
 

	
 
        Returns:
 
@@ -136,20 +218,17 @@ class LDAPPermissions(object):
 
        requested_rules = []
 
        for n, rule in enumerate(self.rules):
 
            rule = "{%d}%s" % (n, rule)
 
            requested_rules.append(rule.rstrip().lstrip().decode("utf-8").encode("utf-8"))
 
            requested_rules.append(rule.rstrip().lstrip().encode("utf-8"))
 

	
 
        return ldap.modlist.modifyModlist({'olcAccess': current_rules}, {'olcAccess': requested_rules})
 

	
 
    def apply(self):
 
    def update(self):
 
        """
 
        Applies permissions requested via the Ansible module configuration.
 

	
 
        The function also produces the necessary JSON output, and terminates
 
        module execution as appropriate.
 
        Updates permissions for an LDAP database.
 

	
 
        Returns:
 

	
 
        Nothing.
 
        True, if an update was performed, False if no update was necessary.
 
        """
 

	
 
        # Fetch the database config based on filter and verify and only one was
 
@@ -157,9 +236,9 @@ class LDAPPermissions(object):
 
        databases = self._get_database()
 

	
 
        if databases == []:
 
            self.module.fail_json(msg="No database matched filter: %s" % self.filter)
 
            raise DatabaseFilteringError("No database matched filter: %s" % self.filter)
 
        elif len(databases) > 1:
 
            self.module.fail_json(msg="More than one databases matched filter: %s" % self.filter)
 
            raise DatabaseFilteringError("More than one databases matched filter: %s" % self.filter)
 

	
 
        database = databases[0]
 

	
 
@@ -168,13 +247,10 @@ class LDAPPermissions(object):
 

	
 
        # Apply modifications if necessary.
 
        if modify_list == []:
 
            self.module.exit_json(changed=False)
 
            return False
 
        else:
 
            try:
 
                self.connection.modify_s(database[0], modify_list)
 
            except ldap.OTHER as e:
 
                self.module.fail_json(msg="Failed to modify permissions. Rule syntax was possibly incorrect. LDAP server responded: %s" % e)
 
            self.module.exit_json(changed=True)
 
            self.connection.modify_s(database[0], modify_list)
 
            return True
 

	
 

	
 
def main():
 
@@ -187,15 +263,45 @@ def main():
 
        argument_spec=dict(
 
            filter=dict(required=True),
 
            rules=dict(required=True),
 
            server_uri=dict(required=False, default="ldapi:///"),
 
            bind_dn=dict(required=False, default=None),
 
            bind_password=dict(required=False)
 
            )
 
        )
 

	
 
    if not ldap_found:
 
        module.fail_json(msg="The Python LDAP module is required")
 

	
 
    ldap_rules = LDAPPermissions(module)
 
    try:
 
        connection = get_ldap_connection(module.params["server_uri"],
 
                                         module.params["bind_dn"],
 
                                         module.params["bind_password"])
 
    except ldap.LDAPError as e:
 
        if e.info:
 
            error_message = "%s: %s" % (e.desc, e.info)
 
        else:
 
            error_message = "%s" % e.desc
 

	
 
        module.fail_json(msg=error_message)
 

	
 
    ldap_permissions = LDAPPermissions(module.params["filter"],
 
                                       module.params["rules"],
 
                                       connection)
 

	
 
    try:
 
        changed = ldap_permissions.update()
 
    except ldap.LDAPError as e:
 
        if e.info:
 
            error_message = "%s: %s" % (e.desc, e.info)
 
        else:
 
            error_message = "%s" % e.desc
 

	
 
        module.fail_json(msg=error_message)
 

	
 
    except DatabaseFilteringError as e:
 
        module.fail_json(msg=DatabaseFilteringError)
 

	
 
    ldap_rules.apply()
 
    module.exit_json(changed=changed)
 

	
 
# Import module snippets.
 
from ansible.module_utils.basic import *
0 comments (0 inline, 0 general)