diff --git a/roles/ldap_server/library/ldap_permissions b/roles/ldap_server/library/ldap_permissions new file mode 100644 index 0000000000000000000000000000000000000000..9371e6de7a944e152d8b4b0fb69476ddd0c3df91 --- /dev/null +++ b/roles/ldap_server/library/ldap_permissions @@ -0,0 +1,202 @@ +#!/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()