Changeset - 8a6ebeaabb26
[Not reviewed]
0 3 0
Branko Majic (branko) - 8 years ago 2017-08-21 20:13:42
branko@majic.rs
MAR-125: Eliminated a couple of warnings:

- Use the file module when cleaning-up the /etc/duply/main/gnupg directory.
- Mark the bind_password in m_ldap_entry and m_ldap_permissions as no_log
parameters.
3 files changed with 11 insertions and 9 deletions:
0 comments (0 inline, 0 general)
roles/backup_client/handlers/main.yml
Show inline comments
 
---
 

	
 
- name: Clean-up GnuPG keyring for import of new keys
 
  shell: "rm -f /etc/duply/main/gnupg/*"
 
  tags:
 
    # [ANSIBLE0007] rm used in place of argument state=absent to file module
 
    #   This task is invoked only if user is very specific about requiring to
 
    #   run the handlers manually as a way to bring the system to consistency
 
    #   after interrupted runs.
 
    - skip_ansible_lint
 
  file:
 
    path: "/etc/duply/main/gnupg"
 
    state: "{{ item }}"
 
    owner: root
 
    group: root
 
    mode: 0700
 
  with_items:
 
    - absent
 
    - directory
 

	
 
- name: Import private keys
 
  command: "{{ gnupg_binary }} --homedir /etc/duply/main/gnupg --import /etc/duply/main/private_keys.asc"
 
  tags:
 
    # [ANSIBLE0012] Commands should not change things if nothing needs doing
 
    #   This task is invoked only if user is very specific about requiring to
 
    #   run the handlers manually as a way to bring the system to consistency
 
    #   after interrupted runs.
 
    - skip_ansible_lint
 

	
 
- name: Import public keys
 
  command: "{{ gnupg_binary }} --homedir /etc/duply/main/gnupg --import /etc/duply/main/public_keys.asc"
 
  when: backup_additional_encryption_keys
roles/ldap_server/library/m_ldap_entry.py
Show inline comments
 
@@ -153,258 +153,258 @@ def get_ldap_connection(uri, bind_dn=None, bind_password=""):
 
    if bind_dn:
 
        connection.simple_bind_s(bind_dn, bind_password)
 
    else:
 
        connection.sasl_interactive_bind_s("", ldap.sasl.external())
 

	
 
    return connection
 

	
 

	
 
class LDAPEntry(object):
 
    """
 
    Implements a convenience wrapper for managing an LDAP entry.
 
    """
 

	
 
    def __init__(self, dn, attributes, connection):
 
        """
 
        Initialises class instance, setting-up the necessary properties.
 

	
 
        Arguments:
 

	
 
        dn
 
          Distinguished name (DN) of an entry.
 

	
 
        attributes
 
          Attributes that should be set for an entry.
 

	
 
        connection
 
          An instance of LDAPObject class that will be used for running queries
 
          against an LDAP server.
 
        """
 

	
 
        self.connection = connection
 
        self.dn = dn
 
        self.attributes = attributes
 

	
 
    def add(self):
 
        """
 
        Adds entry to the LDAP directory.
 

	
 
        Returns:
 

	
 
        True, if entry was added, or had to be updated to match with requested
 
        attributes. False, if no change was necessary.
 
        """
 

	
 
        # If entry already exists with set attributes, only update it.
 
        if self.exists():
 
            return self._update()
 

	
 
        # Otherwise we need to add a new entry.
 
        self.connection.add_s(self.dn, ldap.modlist.addModlist(self.attributes))
 

	
 
        return True
 

	
 
    def remove(self):
 
        """
 
        Removes entry from an LDAP directory.
 

	
 
        Returns:
 

	
 
        True, if entry was removed. False if no change was necessary (entry is
 
        already not present).
 
        """
 

	
 
        if self.exists():
 
            self.connection.delete_s(self.dn)
 

	
 
            return True
 

	
 
        return False
 

	
 
    def append(self):
 
        """
 
        Append attributes to an existing entry. If the entry does not exist,
 
        create it.
 

	
 
        Returns:
 

	
 
        True, if entry was updated with new attribute values or if a new entry
 
        has been created. False if no change was necessary (values are already
 
        present).
 
        """
 

	
 
        if not self.exists():
 
            return self.add()
 

	
 
        attribute_list = self.attributes.keys()
 

	
 
        current_attributes = self.connection.search_s(self.dn, ldap.SCOPE_BASE, attrlist=attribute_list)[0][1]
 

	
 
        # This dictionary will contain all new attributes (or attribute values)
 
        # that should be added to the entry. We can't rely on modifyModlist
 
        # unfortunately, since if the values already exists, it will try to
 
        # remove and re-add them.
 
        new_attributes = {}
 

	
 
        # If attribute is already present, only add the difference between
 
        # requested and current values.
 
        for attribute, values in current_attributes.iteritems():
 
            if attribute in self.attributes:
 
                new_attributes[attribute] = [ item for item in self.attributes[attribute] if item not in values ]
 
            else:
 
                new_attributes[attribute] = values
 

	
 
        modification_list = ldap.modlist.modifyModlist({}, new_attributes)
 

	
 
        if not modification_list:
 
            return False
 

	
 
        self.connection.modify_s(self.dn, modification_list)
 

	
 
        return True
 

	
 
    def replace(self):
 
        """
 
        Replace attributes of an existing entry. If the entry does not exist,
 
        create it.
 

	
 
        Returns:
 

	
 
        True, if entry was updated with new attribute values or if a new entry
 
        has been created. False if no change was necessary (values are already
 
        present).
 
        """
 

	
 
        if not self.exists():
 
            return self.add()
 

	
 
        attribute_list = self.attributes.keys()
 

	
 
        current_attributes = self.connection.search_s(self.dn, ldap.SCOPE_BASE, attrlist=attribute_list)[0][1]
 

	
 
        modification_list = ldap.modlist.modifyModlist(current_attributes,
 
                                                       self.attributes, ignore_oldexistent=1)
 

	
 
        if not modification_list:
 
            return False
 

	
 
        self.connection.modify_s(self.dn, modification_list)
 

	
 
        return True
 

	
 
    def _update(self):
 
        """
 
        Updates an LDAP entry to have the requested attributes.
 

	
 
        Returns:
 

	
 
        True, if LDAP entry was updated. False if no change was necessary (entry
 
        already has the correct attributes).
 
        """
 

	
 
        self.current_attributes = self.connection.search_s(self.dn, ldap.SCOPE_BASE)[0][1]
 

	
 

	
 
        modification_list = ldap.modlist.modifyModlist(self.current_attributes,
 
                                                       self.attributes)
 

	
 
        if not modification_list:
 
            return False
 

	
 
        self.connection.modify_s(self.dn, modification_list)
 

	
 
        return True
 

	
 
    def exists(self):
 
        """
 
        Checks if the entry already exists in LDAP directory or not.
 

	
 
        Returns:
 
        True, if entry exists. False otherwise.
 
        """
 

	
 
        try:
 
            self.connection.search_s(self.dn, ldap.SCOPE_BASE, attrlist=["dn"])
 
        except ldap.NO_SUCH_OBJECT:
 
            return False
 

	
 
        return True
 

	
 

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

	
 
    # Construct the module helper for parsing the arguments.
 
    module = AnsibleModule(
 
        argument_spec=dict(
 
            dn=dict(required=True),
 
            state=dict(required=False, choices=["present", "absent", "append", "replace"], default="present"),
 
            server_uri=dict(required=False, default="ldapi:///"),
 
            bind_dn=dict(required=False, default=None),
 
            bind_password=dict(required=False),
 
            bind_password=dict(required=False, no_log=True),
 
            attributes=dict(required=False, type='dict', default=None),
 
            ),
 
        check_invalid_arguments=False
 
        )
 

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

	
 
    # Extract the attributes. If a single value is provided for an attribute, it
 
    # must be convereted into one-element list. All items must be converted into
 
    # UTF-8 strings otherwise.
 
    attributes = {}
 

	
 
    def repack_value(value):
 
        """
 
        Small helper to repack a single value into list of UTF-8-encoded
 
        strings.
 
        """
 

	
 
        if isinstance(value, list):
 
            value = [ str(i).encode("utf-8") for i in value ]
 
        else:
 
            value = [ str(value).encode("utf-8") ]
 

	
 
        return value
 

	
 
    if module.params["attributes"]:
 
        for name, value in module.params["attributes"].iteritems():
 
            attributes[name] = repack_value(value)
 

	
 
    for name, value in module.params.iteritems():
 
        if name not in module.argument_spec:
 
            attributes.setdefault(name, []).extend(repack_value(value))
 
            attributes[name] = list(set(attributes[name]))
 
    try:
 
        connection = get_ldap_connection(module.params["server_uri"],
 
                                         module.params["bind_dn"],
 
                                         module.params["bind_password"])
 
    except ldap.LDAPError as e:
 
        module.fail_json(msg="LDAP error: %s" % str(e))
 
    state = module.params["state"]
 

	
 
    entry = LDAPEntry(module.params["dn"],
 
                      attributes,
 
                      connection)
 

	
 
    # Add/remove entry as requested.
 
    try:
 
        if state == "present":
 
            changed = entry.add()
 
        elif state == "append":
 
            changed = entry.append()
 
        elif state == "replace":
 
            changed = entry.replace()
 
        else:
 
            changed = entry.remove()
 
    except ldap.LDAPError as e:
 
        module.fail_json(msg="LDAP error: %s" % str(e))
 

	
 
    module.exit_json(changed=changed)
 

	
 

	
 
# Import module snippets.
 
from ansible.module_utils.basic import *
 
main()
roles/ldap_server/library/m_ldap_permissions.py
Show inline comments
 
@@ -78,224 +78,224 @@ m_ldap_permissions:
 
m_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.
 
m_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
 
"""
 

	
 
from ansible.module_utils.basic import *
 

	
 
# 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
 

	
 

	
 
def get_ldap_connection(uri, bind_dn=None, bind_password=""):
 
    """
 
    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.
 
    """
 

	
 
    connection = ldap.initialize(uri)
 

	
 
    if bind_dn:
 
        connection.simple_bind_s(bind_dn, bind_password)
 
    else:
 
        connection.sasl_interactive_bind_s("", ldap.sasl.external())
 

	
 
    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 class instance, setting-up the necessary properties.
 

	
 
        Arguments:
 

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

	
 
        rules
 
          Rules to apply.
 

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

	
 
        self.ldap_filter = ldap_filter
 
        self.rules = rules
 
        self.connection = connection
 

	
 
    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.ldap_filter)
 

	
 
    def _get_modifications(self, database):
 
        """
 
        Returns modification list for updating 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].get("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().encode("utf-8"))
 

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

	
 
    def update(self):
 
        """
 
        Updates permissions for an LDAP database.
 

	
 
        Returns:
 

	
 
        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
 
        # returned.
 
        databases = self._get_database()
 

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

	
 
        database = databases[0]
 

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

	
 
        # Apply modifications if necessary.
 
        if modify_list == []:
 
            return False
 
        else:
 
            self.connection.modify_s(database[0], modify_list)
 
            return 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, type='list'),
 
            server_uri=dict(required=False, default="ldapi:///"),
 
            bind_dn=dict(required=False, default=None),
 
            bind_password=dict(required=False)
 
            bind_password=dict(required=False, no_log=True)
 
            )
 
        )
 

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

	
 
    try:
 
        connection = get_ldap_connection(module.params["server_uri"],
 
                                         module.params["bind_dn"],
 
                                         module.params["bind_password"])
 
    except ldap.LDAPError as e:
 
        module.fail_json(msg="LDAP error: %s" % str(error_message))
 

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

	
 
    try:
 
        changed = ldap_permissions.update()
 

	
 
    except ldap.LDAPError as e:
 
        module.fail_json(msg="LDAP error: %s" % str(e))
 

	
 
    except DatabaseFilteringError as e:
 
        module.fail_json(msg="Module error: %s" % str(e))
 

	
 
    module.exit_json(changed=changed)
 

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