Files @ d1a4cfa37a3f
Branch filter:

Location: majic-ansible-roles/roles/ldap_server/library/ldap_permissions

branko
MAR-1: Added a small module for managing the LDAP permissions.
#!/usr/bin/env python

DOCUMENTATION = """
---
module: ldap_permissions
short_description: Sets permissions/ACL for LDAP database.
description:
  - Sets permissions (access control list) for LDAP database.
version_added: 1.7.2
author: Branko Majic
notes:
  - Requires the python-ldap Python package on remote host. For Debian and
    derivatives, this is as easy as apt-get install python-ldap.
requirements:
  - python-ldap
options:
  filter:
    description:
      - LDAP filter that should be used for locating the database on which the
        ACL rules should be applied. This filter will be used for search under
        the C(cn=config) base DN. For regular user databases, the filter should
        probably be based on the C(olcSuffix) attribute. The filter must result
        in a unique entry.
    required: true
    default: ""
  rules:
    description:
      - LDAP rules that should be applied to the LDAP database. The rules should
        be provided as a list of strings. Each string should be an access rule
        as described in OpenLDAP administrator guide at
        U(http://www.openldap.org/doc/admin24/access-control.html). Use long
        format for specifying this parameter (see examples below).
    required: true
    default: ""
"""

EXAMPLES = """
# Set-up of rules for regular database.
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
# Set-up rules for a configuration database. This time with a single rule in a
# single line.
ldap_permissions:
  - filter: '(olcDatabase={0}config)'
    rules:
      - to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage by * break
"""

# Try to load the Python LDAP module.
try:
    import ldap
    import ldap.sasl
    import ldap.modlist
except ImportError:
    ldap_found = False
else:
    ldap_found = True


class LDAPPermissions(object):
    """
    This class encapsulates functionality for applying ACL to an LDAP database.
    """

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

        self.module = module
        self.filter = module.params["filter"]
        self.rules = module.params["rules"]
        self._connect()

    def _connect(self):
        """
        Initialises LDAP connection and binds to the LDAP server.

        Binding is done using the SASL EXTERNAL mechanism.

        Returns:

        Nothing.
        """

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

    def _get_database(self):
        """
        Retrieves the requested database entry.

        Returns:

        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)

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

        Returns:

        Modification list. The format is suitable for use with functions
        ldap.modify() and ldap.modify_s(). An empty list will be returned if no
        changes are necessary.
        """

        # Fetch the list of current rules.
        current_rules = database[1]["olcAccess"]

        # Set-up list of requested rules.
        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"))

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

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

        The function also produces the necessary JSON output, and terminates
        module execution as appropriate.

        Returns:

        Nothing.
        """

        # Fetch the database config based on filter and verify and only one was
        # returned.
        databases = self._get_database()

        if databases == []:
            self.module.fail_json(msg="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)

        database = databases[0]

        # Set-up the modification list.
        modify_list = self._get_modifications(database)

        # Apply modifications if necessary.
        if modify_list == []:
            self.module.exit_json(changed=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)


def main():
    """
    Runs the module.
    """

    # Construct the module helper for parsing the arguments.
    module = AnsibleModule(
        argument_spec=dict(
            filter=dict(required=True),
            rules=dict(required=True),
            )
        )

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

    ldap_rules = LDAPPermissions(module)

    ldap_rules.apply()

# Import module snippets.
from ansible.module_utils.basic import *
main()