├── test ├── library ├── scan_user_group.yml ├── scan_cron.yml ├── scan_sudoers.yml └── scan_processes.yml ├── README.md └── library ├── scan_processes.py ├── scan_user_group.py ├── scan_cron.py └── scan_sudoers.py /test/library: -------------------------------------------------------------------------------- 1 | ../library -------------------------------------------------------------------------------- /test/scan_user_group.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Test scan_user_group - Python 2.7" 3 | hosts: "localhost" 4 | gather_facts: False 5 | vars:3 6 | ansible_python_interpreter: "/usr/bin/python2.7" 7 | tasks: 8 | - name: "Scan users and groups" 9 | scan_user_group: 10 | become: True 11 | 12 | - name: "Display local user facts" 13 | debug: 14 | var: "local_users" 15 | 16 | - name: "Display local group facts" 17 | debug: 18 | var: "local_groups" 19 | 20 | 21 | - name: "Test scan_user_group - Python 3.7" 22 | hosts: "localhost" 23 | gather_facts: False 24 | vars: 25 | ansible_python_interpreter: "/usr/bin/python3.7" 26 | tasks: 27 | - name: "Scan users and groups" 28 | scan_user_group: 29 | become: True 30 | 31 | - name: "Display local user facts" 32 | debug: 33 | var: "local_users" 34 | 35 | - name: "Display local group facts" 36 | debug: 37 | var: "local_groups" 38 | -------------------------------------------------------------------------------- /test/scan_cron.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Test scan_cron - Python 2.7" 3 | hosts: "localhost" 4 | gather_facts: False 5 | vars: 6 | ansible_python_interpreter: "/usr/bin/python2.7" 7 | tasks: 8 | - name: "Scan Cron - all data" 9 | scan_cron: 10 | become: True 11 | 12 | - name: "Display all cron data" 13 | debug: 14 | var: "cron" 15 | 16 | - name: "Scan Cron - no parsed data" 17 | scan_cron: 18 | output_parsed_configs: False 19 | become: True 20 | 21 | - name: "Display only cron raw configs" 22 | debug: 23 | var: "cron" 24 | 25 | - name: "Scan Cron - no raw configuration data" 26 | scan_cron: 27 | output_raw_configs: False 28 | become: True 29 | 30 | - name: "Display only cron parsed data" 31 | debug: 32 | var: "cron" 33 | 34 | - name: "Test scan_cron - Python 3.7" 35 | hosts: "localhost" 36 | gather_facts: False 37 | vars: 38 | ansible_python_interpreter: "/usr/bin/python3.7" 39 | tasks: 40 | - name: "Scan Cron - all data" 41 | scan_cron: 42 | become: True 43 | 44 | - name: "Display all cron data" 45 | debug: 46 | var: "cron" 47 | 48 | - name: "Scan Cron - no parsed data" 49 | scan_cron: 50 | output_parsed_configs: False 51 | become: True 52 | 53 | - name: "Display only cron raw configs" 54 | debug: 55 | var: "cron" 56 | 57 | - name: "Scan Cron - no raw configuration data" 58 | scan_cron: 59 | output_raw_configs: False 60 | become: True 61 | 62 | - name: "Display only cron parsed data" 63 | debug: 64 | var: "cron" 65 | -------------------------------------------------------------------------------- /test/scan_sudoers.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Test scan_sudoers - Python 2.7" 3 | hosts: "localhost" 4 | gather_facts: False 5 | vars: 6 | ansible_python_interpreter: "/usr/bin/python2.7" 7 | tasks: 8 | - name: "Scan Sudoers" 9 | scan_sudoers: 10 | become: True 11 | 12 | - name: "Display all sudoers data" 13 | debug: 14 | var: "sudoers" 15 | 16 | - name: "Scan Sudoers - no parsed data" 17 | scan_sudoers: 18 | output_parsed_configs: False 19 | become: True 20 | 21 | - name: "Display only sudoers raw configs" 22 | debug: 23 | var: "sudoers" 24 | 25 | - name: "Scan Sudoers - no raw configuration data" 26 | scan_sudoers: 27 | output_raw_configs: False 28 | become: True 29 | 30 | - name: "Display only sudoers parsed configs" 31 | debug: 32 | var: "sudoers" 33 | 34 | - name: "Test scan_sudoers - Python 3.7" 35 | hosts: "localhost" 36 | gather_facts: False 37 | vars: 38 | ansible_python_interpreter: "/usr/bin/python3.7" 39 | tasks: 40 | - name: "Scan Sudoers - all data" 41 | scan_sudoers: 42 | become: True 43 | 44 | - name: "Display all sudoers data" 45 | debug: 46 | var: "sudoers" 47 | 48 | - name: "Scan Sudoers - no parsed data" 49 | scan_sudoers: 50 | output_parsed_configs: False 51 | become: True 52 | 53 | - name: "Display only sudoers raw configs" 54 | debug: 55 | var: "sudoers" 56 | 57 | - name: "Scan Sudoers - no raw configuration data" 58 | scan_sudoers: 59 | output_raw_configs: False 60 | become: True 61 | 62 | - name: "Display only sudoers parsed configs" 63 | debug: 64 | var: "sudoers" 65 | -------------------------------------------------------------------------------- /test/scan_processes.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Test scan_processes - Python 2.7" 3 | hosts: "localhost" 4 | gather_facts: False 5 | vars: 6 | ansible_python_interpreter: "/usr/bin/python2.7" 7 | tasks: 8 | - name: "Scan Processes - Show Everything" 9 | scan_processes: 10 | output_ps_stdout_lines: True 11 | become: True 12 | 13 | - name: "Display Processes" 14 | debug: 15 | var: "running_processes" 16 | 17 | - name: "Collect only process standard out lines" 18 | scan_processes: 19 | output_ps_stdout_lines: True 20 | output_parsed_processes: False 21 | become: True 22 | 23 | - name: "Display Processes - only stdout lines" 24 | debug: 25 | var: "running_processes" 26 | 27 | - name: "Collect only parsed process data" 28 | scan_processes: 29 | become: True 30 | 31 | - name: "Display Parsed Processes only" 32 | debug: 33 | var: "running_processes" 34 | 35 | - name: "Test scan_processes - Python 3.7" 36 | hosts: "localhost" 37 | gather_facts: False 38 | vars: 39 | ansible_python_interpreter: "/usr/bin/python3.7" 40 | tasks: 41 | - name: "Scan Processes - Show Everything" 42 | scan_processes: 43 | output_ps_stdout_lines: True 44 | become: True 45 | 46 | - name: "Display Processes" 47 | debug: 48 | var: "running_processes" 49 | 50 | - name: "Collect only process standard out lines" 51 | scan_processes: 52 | output_ps_stdout_lines: True 53 | output_parsed_processes: False 54 | become: True 55 | 56 | - name: "Display Processes - only stdout lines" 57 | debug: 58 | var: "running_processes" 59 | 60 | - name: "Collect only parsed process data" 61 | scan_processes: 62 | become: True 63 | 64 | - name: "Display Parsed Processes only" 65 | debug: 66 | var: "running_processes" 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ansible-fact 2 | ## Table of Contents 3 | 4 | 5 | 1. [Table of Contents](#table-of-contents) 6 | 2. [About](#about) 7 | 3. [Available Modules](#available-modules) 8 | 4. [Module Documentation](#module-documentation) 9 | 5. [Contributions](#contributions) 10 | 1. [Guidelines](#guidelines) 11 | 6. [Author(s)](#authors) 12 | 13 | 14 | 15 | ## About 16 | The concept of this project is to perform Infrastructure-as-Code in Reverse (i.e. iacir - pronounced: aya sir). 17 | 18 | `ansible-fact` consists of a collection of Ansible fact collectors to be able to generate structured data and harvest system configurations. The goal is to be able to automatically collect all aspects of a system's configuration through modules. 19 | 20 | ## Available Modules 21 | | Module Name | Description | Test Playbook | 22 | | --- | --- | --- | 23 | | [scan_cron](library/scan_cron.py) | Collects all cron data from a system and converts to structured data | [scan_cron.yml](test/scan_cron.yml) | 24 | | [scan_processes](library/scan_processes.py) | Collects currently running processes from a system and converts to structured data | [scan_processes.yml](test/scan_processes.yml) | 25 | | [scan_sudoers](library/scan_sudoers.py) | Collects all sudoers configurations and converts to structured data | [scan_sudoers.yml](test/scan_sudoers.yml) | 26 | | [scan_user_group](library/scan_user_group.py) | Collects all local user and group data from `/etc/shadow`, `/etc/gshadow`, `/etc/passwd`, and `/etc/group`, and merges into structured data. | [scan_user_group.yml](test/scan_user_group.yml) 27 | 28 | ## Module Documentation 29 | All module documentation can be found in each respective module, as with any Ansible module. 30 | 31 | ## Contributions 32 | Please feel free to openly contribute to this project. All code will be reviewed prior to merging. 33 | 34 | ### Guidelines 35 | * Please perform all development and pull requests against the `dev` branch of this repository. 36 | * If a particular fact collector can apply to many different Operating Systems, please try and accommodate all Operating System implementations in an attempt to keep this project platform agnostic. 37 | * Please include a test playbook in the [test](test) directory. 38 | * Please place your modules in the [library](library) directory. 39 | * Please document your code and modules thoroughly via comments and by following [Ansible's Module Development Documentation](https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html#starting-a-new-module). 40 | 41 | ## Author(s) 42 | [Andrew J. Huffman](https://github.com/ahuffman) 43 | -------------------------------------------------------------------------------- /library/scan_processes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright: (c) 2019, Andrew J. Huffman 4 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 | 6 | ANSIBLE_METADATA = {'metadata_version': '1.1', 7 | 'status': ['preview'], 8 | 'supported_by': 'community'} 9 | 10 | DOCUMENTATION = ''' 11 | --- 12 | module: scan_processes 13 | short_description: Collects currently running processes on a system 14 | version_added: "2.8" 15 | description: 16 | - "Collects the currently running processes on a system at the time the module is run." 17 | - "This module presents the currently running proceses as ansible_facts" 18 | output_ps_stdout_lines: 19 | description: 20 | - Whether or not to output the collected standard out lines from the 'ps auxww' command 21 | default: False 22 | required: False 23 | output_parsed_processes: 24 | description: 25 | - Whether or not to output parsed data from the 'ps auxww' command. 26 | default: True 27 | required: False 28 | author: 29 | - Andrew J. Huffman (@ahuffman) 30 | ''' 31 | 32 | EXAMPLES = ''' 33 | # Collect running processes and output parsed data 34 | - name: "Collect current running processes" 35 | scan_processes: 36 | 37 | # Collect only standard out lines from the ps auxww command 38 | - name: "Collect process command output" 39 | scan_processes: 40 | output_ps_stdout_lines: True 41 | output_parsed_processes: False 42 | 43 | # Collect both parsed process data and 'ps auxww' command standard out 44 | - name: "Collect all process data" 45 | scan_processes: 46 | output_ps_stdout_lines: True 47 | ''' 48 | 49 | RETURN = ''' 50 | running_processes: 51 | processes: 52 | - command: /usr/lib/systemd/systemd --switched-root --system --deserialize 33 53 | cpu_percentage: '0.0' 54 | memory_percentage: '0.0' 55 | pid: '1' 56 | resident_size: '5036' 57 | start: Jul08 58 | stat: Ss 59 | teletype: '?' 60 | time: '3:32' 61 | user: root 62 | ... 63 | ps_stdout_lines: 64 | - root 1 0.0 0.0 171628 5056 ? Ss Jul08 3:32 /usr/lib/systemd/systemd --switched-root --system --deserialize 33 65 | ... 66 | total_running_processes: 359 67 | ''' 68 | 69 | from ansible.module_utils.basic import AnsibleModule 70 | import os, re, subprocess 71 | from os.path import isfile, isdir, join 72 | 73 | def main(): 74 | module_args = dict( 75 | output_ps_stdout_lines=dict( 76 | type='bool', 77 | default=False, 78 | required=False 79 | ), 80 | output_parsed_processes=dict( 81 | type='bool', 82 | default=True, 83 | required=False 84 | ) 85 | ) 86 | 87 | result = dict( 88 | changed=False, 89 | original_message='', 90 | message='' 91 | ) 92 | 93 | module = AnsibleModule( 94 | argument_spec=module_args, 95 | supports_check_mode=True 96 | ) 97 | 98 | params = module.params 99 | 100 | def get_processes(): 101 | re_header = re.compile(r'^USER+.*') 102 | proc_stats = dict() 103 | procs = list() 104 | count = 0 105 | running = subprocess.check_output(["ps auxww"], universal_newlines=True, shell=True) 106 | for l in running.split('\n'): 107 | if len(l) > 0 and re_header.search(l) is None: 108 | procs.append(l.replace('\n', '').replace('\t', ' ')) 109 | count += 1 110 | proc_stats['stdout'] = procs 111 | proc_stats['total_running_processes'] = count 112 | return proc_stats 113 | 114 | def parse_process_data(procs): 115 | re_ps = re.compile(r'^(?P[\w\+\-\_\$]+)\s+(?P[0-9]+)\s+(?P[0-9\.]+)\s+(?P[0-9\.]+)\s+(?P[0-9]+)\s+(?P[0-9]+)\s+(?P[a-zA-Z0-9\?\/]+)\s+(?P[DIRSTtWXZ\[A-Za-z0-9\:]+)\s+(?P