--- - name: Identify Packages with CVE Vulnerabilities hosts: all gather_facts: true vars: cve_nvd_api_url: "https://services.nvd.nist.gov/rest/json/cves/2.0" output_file: "/tmp/cve_report_{{ ansible_date_time.iso8601_basic_short }}.json" results_per_page: 2000 tasks: - name: Detect OS family and set package manager set_fact: pkg_mgr: "{{ 'apt' if ansible_os_family == 'Debian' else 'apk' if ansible_os_family == 'Alpine' else 'unknown' }}" - name: Ensure required packages are installed package: name: - curl - jq state: present - name: Get installed packages with versions (Debian/Ubuntu) command: dpkg-query -W -f='${Package}\t${Version}\n' register: installed_packages_debian changed_when: false when: ansible_os_family == 'Debian' - name: Get installed packages with versions (Alpine) command: apk info -vv register: installed_packages_alpine changed_when: false when: ansible_os_family == 'Alpine' - name: Parse package list into dictionary set_fact: package_dict: "{{ installed_packages_debian.stdout | default('') | split('\n') | select('match', '^.+\t.+$') | map('regex_replace', '^(.+?)\\t(.+)$', '{\"name\": \"\\1\", \"version\": \"\\2\"}') | map('from_json') | list }}" when: ansible_os_family == 'Debian' - name: Parse Alpine package list into dictionary set_fact: package_dict: "{{ installed_packages_alpine.stdout | default('') | split('\n') | select('match', '^.+-.+$') | map('regex_replace', '^(.+?)-([0-9].+)$', '{\"name\": \"\\1\", \"version\": \"\\2\"}') | map('from_json') | list }}" when: ansible_os_family == 'Alpine' - name: Query NVD CVE database for each package uri: url: "{{ cve_nvd_api_url }}" method: GET return_content: true validate_certs: false headers: User-Agent: "Ansible-CVE-Scanner/1.0" register: nvd_response failed_when: false until: nvd_response.status == 200 retries: 3 delay: 2 - name: Extract CVE data from NVD response set_fact: cve_data: "{{ nvd_response.content | from_json | json_query('vulnerabilities[*]') }}" when: nvd_response.status == 200 - name: Match CVEs with installed packages set_fact: cve_findings: >- {{ cve_findings | default([]) + [{ 'package': item.package_name, 'version': item.version, 'cves': cve_data | selectattr('cve.id', 'defined') | selectattr('cve.descriptions[*].value', 'contains', item.package_name) | map(attribute='cve') | list, 'hostname': ansible_hostname, 'ip_address': ansible_default_ipv4.address, 'os': ansible_distribution + ' ' + ansible_distribution_version, 'scan_date': ansible_date_time.iso8601 }] }} loop: "{{ package_dict }}" loop_control: loop_var: item vars: package_name: "{{ item.name }}" version: "{{ item.version }}" - name: Filter packages with CVEs set_fact: affected_packages: "{{ cve_findings | selectattr('cves', 'defined') | selectattr('cves', 'length', 'gt', 0) | list }}" - name: Generate CVE report JSON copy: dest: "{{ output_file }}" content: "{{ affected_packages | to_json(indent=2) }}" mode: '0600' - name: Display CVE summary debug: msg: "Found {{ affected_packages | length }} packages with CVEs. Report saved to {{ output_file }}" - name: Return CVE findings set_fact: cve_report: hostname: ansible_hostname ip_address: ansible_default_ipv4.address os: ansible_distribution + ' ' + ansible_distribution_version total_packages: package_dict | length packages_with_cves: affected_packages | length findings: affected_packages scan_date: ansible_date_time.iso8601 report_file: output_file