Add auditd.py module

It is possible now to pass a list of paths where
suid/sgid binaries should not be find.
The python module uses multiprocessing and can be buggy.
All ideas how to do it more safely (too high I/O and so on)
and keep the performance in the same time, are highly appreciated.

Change-Id: Icd1ae445fb8fed1ea08842606f371223f72bc82f
Closes-PROD: https://mirantis.jira.com/browse/PROD-21273
diff --git a/_modules/auditd.py b/_modules/auditd.py
new file mode 100644
index 0000000..d5445a8
--- /dev/null
+++ b/_modules/auditd.py
@@ -0,0 +1,90 @@
+"""
+Module for finding executable files with suid or sgid bit.
+It was implemented in opposite to using __salt__[cmd.run](find)
+due to complexity of filter logic in audit rules template.
+"""
+
+import multiprocessing.dummy
+import functools
+import logging
+import stat
+import os
+import salt.utils
+
+log = logging.getLogger(__name__)
+
+def __virtual__():
+    if salt.utils.is_windows():
+        return False
+    return 'auditd'
+
+def _find_privileged_files(path, filtered=None, mounts=None):
+    """Find executable files with suid or sbig bit.
+    The function accepts list of paths wich will be skiped in search.
+    """
+
+    mounts = mounts or []
+    filtered = filtered or []
+    e_bits = stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH
+    s_bits = stat.S_ISUID | stat.S_ISGID
+
+    for root, dirs, files in os.walk(path, topdown=True):
+        if (root in mounts and root != path) or root in filtered:
+            # Filter all mount points which lies down of the path
+            # (implements the `-xdev` of the find command),
+            # as well as all "filtered" paths (`-prune`/`-not`).
+            dirs[:] = []
+            files[:] = []
+        for fname in files:
+            fpath = os.path.join(root, fname)
+            if os.path.islink(fpath):
+                continue
+            mode = os.stat(fpath).st_mode
+            if (mode & e_bits) and (mode & s_bits):
+                yield fpath
+
+def find_privileged(filter_fs=None, filter_paths=None, filter_mounts=True):
+
+    filter_fs = filter_fs or []
+    filtered = filter_paths or []
+    mounts = []
+
+    for part, mount_opts in __salt__['mount.active']().items():
+        if mount_opts['fstype'] in filter_fs:
+            # Must be skipped according to the fstype filter.
+            log.debug("Add '%s' to filtered (filtered fs)", part)
+            filtered.append(part)
+            continue
+        if filter_mounts:
+            if set(['noexec', 'nosuid']) & set(mount_opts['opts']):
+                # It has noexec/nosuid option in mount options.
+                # Must be skipped.
+                log.debug("Add '%s' to filtered (mount options)", part)
+                filtered.append(part)
+                continue
+        if part == '/':
+            continue
+        mounts.append(part)
+
+    # Collect all directories for parallel scanning
+    scan_dirs = ['/'+d for d in os.listdir('/') if os.path.isdir('/'+d)]
+
+    # Add all mount points into the scan dirs list
+    for mount_point in mounts:
+        if mount_point in scan_dirs:
+            # It is already in the scan list
+            continue
+        if any([mount_point.startswith(f) for f in filtered]):
+            # Such mount point must be skipped
+            # (implements `-prune`/`-not` behavior of the find command)
+            continue
+        scan_dirs.append(mount_point)
+
+    pool = multiprocessing.dummy.Pool(len(scan_dirs))
+    find = functools.partial(_find_privileged_files,
+                             filtered=filtered,
+                             mounts=mounts)
+
+    p = pool.map_async(find, scan_dirs)
+
+    return sorted([item for res in p.get() for item in res])