Boosting Container Image Security Using Wazuh and Trivy

This article draws inspiration from the Wazuh blog post on enhancing container image security with Wazuh and Trivy.

Containerization has revolutionized software development and deployment, offering scalability and efficiency.

However, this agility can introduce security risks if container images aren’t properly secured.

Vulnerabilities within these images can expose your entire system to threats. This is where the combined power of Wazuh and Trivy comes in.

These open-source tools provide a comprehensive solution for boosting your container image security, ensuring your applications are protected from the ground up.

Why Container Image Security is Crucial

Container images are the building blocks of your containerized applications.

A single vulnerability within an image can compromise your entire system, leading to:

Data Breaches: Sensitive data can be exposed to unauthorized access.

Service Disruptions: Attacks can cause downtime and disrupt business operations.

Reputational Damage: Security incidents can erode trust and damage your brand.

Proactive security measures, like integrating Wazuh and Trivy, are essential for mitigating these risks.

Introducing Trivy: Your Vulnerability Scanner

Trivy is a fast and comprehensive open-source vulnerability scanner that seamlessly integrates into your CI/CD pipeline. It scans your container images for:

Operating System Vulnerabilities: Detects outdated packages and known weaknesses in the base OS.

Application Dependencies: Identifies vulnerabilities in libraries and frameworks your application uses.

Misconfigurations: Uncovers security misconfigurations that can widen your attack surface.

Pre-requirements

Follow the same Trivy installation steps as in the original article.

Additionally, you need to install the Docker library for Python:

pip install docker

Integrating Trivy with Wazuh

Just like in the original article, you’ll need to craft an integration script to connect Trivy and Wazuh.

However, unlike the original, I prefer using Python for this task to enhance flexibility and control.

The script should be created on the agent you intend to monitor. That said, it can also be executed from the server - just keep in mind that it must run on the system where you plan to perform the scanning.

Let’s start by setting up a directory for the script.

mkdir /var/ossec/custom-script

This directory will serve as the home for our script. Now, it’s time to build the scanning script itself.

Create the script using the following command:

vi /var/ossec/custom-script/trivy_scanner.py

Here’s how the script will look once we set it up.

#!/usr/bin/env python
import json
import subprocess
import sys
import docker


def get_all_images():
    client = docker.from_env()
    image_names = list({image.tags[0] for image in client.images.list() if image.tags})
    return image_names


def scan_result(images: list):
    all_results = []
    for image in images:
        if not images or images == [""]:
            print("No images found. Exiting...")
            sys.exit(1)
        try:
            result = subprocess.run(['trivy', 'image', image, '--format', 'json', '--scanners', 'vuln'],
                                    capture_output=True, text=True,
                                    check=True)
            trivy_json = json.loads(result.stdout)
            for result in trivy_json.get("Results", []):
                for vuln in result.get("Vulnerabilities", []):
                    pkg_name = vuln.get("PkgName", "N/A")
                    installed_version = vuln.get("InstalledVersion", "N/A")
                    vuln_id = vuln.get("VulnerabilityID", "N/A")
                    severity = vuln.get("Severity", "N/A")
                    title = vuln.get("Title", "N/A")
                    trivy_result = {
                        'image': image,
                        'package': pkg_name,
                        'version': installed_version,
                        'vulnerability_id': vuln_id,
                        'severity': severity,
                        'title': title
                    }
                    print(json.dumps(trivy_result))
        except (FileNotFoundError, subprocess.CalledProcessError, json.JSONDecodeError) as error:
            all_results.append({"error": str(error)})  # Log errors


if __name__ == '__main__':
    scan_result(get_all_images())

Save the script and assign the appropriate access permissions to ensure it runs securely.

Execute these commands to set the permissions:

chmod 750 /var/ossec/custom-script/trivy_scanner.py
chown root:wazuh -R /var/ossec/custom-script/

Next, it’s time to craft rules for detecting vulnerabilities. Create the file /var/ossec/etc/rules/trivy_rules.xml with the following content.


<group name="trivy,">
  <!-- Parent Rule for Trivy alerts -->
  <rule id="100201" level="0">
    <decoded_as>json</decoded_as>
    <description>Trivy alert detected.</description>
  </rule>

  <!-- This rule detects a critical severity vulnerability in a container image -->
  <rule id="100202" level="14">
    <if_sid>100201</if_sid>
    <field name="severity">CRITICAL</field>
    <description>Trivy alert [CRITICAL]: Vulnerabilty '$(vulnerability_id)' detected in package '$(package)' version '$(version)' on container image '$(image)'.</description>
  </rule>

  <!-- This rule detects a high severity vulnerability in a container image -->
  <rule id="100203" level="12">
    <if_sid>100201</if_sid>
    <field name="severity">HIGH</field>
    <description>Trivy alert [HIGH]: Vulnerabilty '$(vulnerability_id)' detected in package '$(package)' version '$(version)' on container image '$(image)'.</description>
  </rule>

  <!-- This rule detects a medium severity vulnerability in a container image -->
  <rule id="100204" level="7">
    <if_sid>100201</if_sid>
    <field name="severity">MEDIUM</field>
    <description>Trivy alert [MEDIUM]: Vulnerabilty '$(vulnerability_id)' detected in package '$(package)' version '$(version)' on container image '$(image)'.</description>
  </rule>

  <!-- This rule detects a low severity vulnerability in a container image -->
  <rule id="100205" level="4">
    <if_sid>100201</if_sid>
    <field name="severity">LOW</field>
    <description>Trivy alert [LOW]: Vulnerabilty '$(vulnerability_id)' detected in package '$(package)' version '$(version)' on container image '$(image)'.</description>
  </rule>

  <!-- This rule detects an unknown severity vulnerability in a container image -->
  <rule id="100206" level="7">
    <if_sid>100201</if_sid>
    <field name="severity">UNKNOWN</field>
    <description>Trivy alert [UNKNOWN]: Vulnerabilty '$(vulnerability_id)' detected in package '$(package)' version '$(version)' on container image '$(image)'.</description>
  </rule>
</group>

Next, configure Wazuh to execute our script by editing the ossec.conf file. Open it with:

vi /var/ossec/etc/ossec.conf

Add the script execution details as follows:

<wodle name="command">
  <disabled>no</disabled>
  <command>/var/ossec/custom-script/trivy_scanner.py</command>
  <interval>6d</interval>
  <ignore_output>no</ignore_output>
  <run_on_start>yes</run_on_start>
  <timeout>0</timeout>
</wodle>

You can adjust the script’s run interval to suit your needs; in my example, it’s set to every 6 days.

Save the changes and restart the services:

systemctl restart wazuh-manager.service

Then, restart the agent:

systemctl restart wazuh-agent

Once completed, you’ll start receiving scan results. Here’s an example event:

2025 Mar 28 14:24:07 compute-vm-14-28-100-ssd-1740727139038->command_
Rule: 100202 (level 14) -> 'Trivy alert [CRITICAL]: Vulnerabilty 'CVE-2023-45853' detected in package 'zlib1g' version '1:1.2.13.dfsg-1' on container image 'nginx:stable'.'
{"image": "nginx:stable", "package": "zlib1g", "version": "1:1.2.13.dfsg-1", "vulnerability_id": "CVE-2023-45853", "severity": "CRITICAL", "title": "zlib: integer overflow and resultant heap-based buffer overflow in zipOpenNewFileInZip4_6"}
image: nginx:stable
package: zlib1g
version: 1:1.2.13.dfsg-1
vulnerability_id: CVE-2023-45853
severity: CRITICAL
title: zlib: integer overflow and resultant heap-based buffer overflow in zipOpenNewFileInZip4_6

Best Practices for Container Security Scanning

To maximize the effectiveness of your Wazuh and Trivy integration, consider the following best practices:

  • Scan frequency: Adjust the scan interval based on your deployment cadence. Environments with frequent image updates may benefit from daily scans, while more stable environments can use the default 6-day interval. The key is to ensure that newly pulled images are scanned before they enter production workloads.
  • Severity-based alerting: Configure your notification channels to differentiate between severity levels. Critical and high-severity vulnerabilities should trigger immediate notifications to the security team, while medium and low findings can be aggregated into periodic reports for review during regular security operations.
  • Base image management: Prioritize scanning your base images, as vulnerabilities in base layers propagate to all derived images. Maintain a curated registry of approved base images that have been scanned and validated. This reduces the overall attack surface across your container fleet.
  • Integration with CI/CD pipelines: While this guide focuses on runtime scanning, consider extending Trivy scanning into your build pipeline. Catching vulnerabilities before images reach production is always preferable to detecting them in a running environment.
  • Dashboard visualization: Create dedicated Wazuh dashboard panels for Trivy alerts to provide a centralized view of your container security posture. Group findings by image name, severity, and CVE identifier to facilitate efficient triage and remediation planning.

See also