- Fix ansible_default_ipv4 undefined issue with fallback to ansible_ssh_host - Simplify disk space analyzer to avoid complex JSON parsing - Update Docker cleanup to handle missing Docker gracefully - Update log archiver to handle missing rotated logs gracefully - All playbooks now provide comprehensive JSON reports - Tested successfully on Ubuntu 20.04/22.04/24.04, Debian 11/12/13, and Alpine
131 lines
4.9 KiB
YAML
131 lines
4.9 KiB
YAML
---
|
|
- 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
|
|
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
|
|
|
|
- name: Parse NVD response
|
|
set_fact:
|
|
nvd_data: "{{ nvd_response.content | from_json | default({}) }}"
|
|
when: nvd_response.status == 200
|
|
|
|
- name: Extract CVE descriptions
|
|
set_fact:
|
|
cve_descriptions: >-
|
|
{{ nvd_data.vulnerabilities | default([]) |
|
|
map(attribute='cve') | default([]) |
|
|
map(attribute='descriptions') | default([]) |
|
|
flatten |
|
|
map(attribute='value') | default([]) |
|
|
select('string') | list }}
|
|
when: nvd_response.status == 200
|
|
|
|
- name: Match packages with CVE mentions
|
|
set_fact:
|
|
cve_findings: >-
|
|
{{ cve_findings | default([]) +
|
|
[{
|
|
'package': item.name,
|
|
'version': item.version,
|
|
'cve_count': cve_descriptions | default([]) | select('search', item.name | default('')) | length,
|
|
'hostname': ansible_hostname,
|
|
'ip_address': ansible_default_ipv4.address | default(ansible_ssh_host | default('unknown')),
|
|
'os': ansible_distribution + ' ' + ansible_distribution_version,
|
|
'scan_date': ansible_date_time.iso8601
|
|
}]
|
|
}}
|
|
loop: "{{ package_dict }}"
|
|
when: nvd_response.status == 200
|
|
|
|
- name: Set CVE findings when NVD query failed
|
|
set_fact:
|
|
cve_findings: >-
|
|
{{ cve_findings | default([]) +
|
|
[{
|
|
'package': item.name,
|
|
'version': item.version,
|
|
'cve_count': 0,
|
|
'note': 'CVE database query failed',
|
|
'hostname': ansible_hostname,
|
|
'ip_address': ansible_default_ipv4.address | default(ansible_ssh_host | default('unknown')),
|
|
'os': ansible_distribution + ' ' + ansible_distribution_version,
|
|
'scan_date': ansible_date_time.iso8601
|
|
}]
|
|
}}
|
|
loop: "{{ package_dict }}"
|
|
when: nvd_response.status != 200
|
|
|
|
- name: Filter packages with CVEs
|
|
set_fact:
|
|
affected_packages: "{{ cve_findings | selectattr('cve_count', 'defined') | selectattr('cve_count', '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 | default(ansible_ssh_host | default('unknown'))
|
|
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
|