Scanner integration¶
Cheery’s requirement was clear: every scanner must feed into DefectDojo automatically, without manual CSV downloads or copy-and-paste. The goal is that when a vulnerability is found, it appears in DefectDojo within minutes, not days. This runbook covers the integration configuration for each scanner: Trivy (container images), OpenVAS/Greenbone (infrastructure), GitLab SAST (application code), Wazuh (host OS packages), and manual penetration test imports. Each scanner uses DefectDojo’s import API with a dedicated token stored in Vault, and failures alert Cheery via Graylog.
DefectDojo import API¶
All scanner integrations use the same API endpoint. The key parameters are:
scan_type: the scanner type identifier that DefectDojo uses to select the correct parserengagement: the ID of the Engagement to import into (obtain from the DefectDojo UI or API)file: the scan results fileactive: whether to mark imported findings as active (alwaystruefor automated imports)verified: whether to mark findings as verified (leavefalsefor automated imports; settrueafter human review)
curl -X POST https://defectdojo.golemtrust.am/api/v2/import-scan/ \
-H "Authorization: Token ${DEFECTDOJO_API_TOKEN}" \
-F "scan_type=Trivy Scan" \
-F "engagement=42" \
-F "file=@trivy-results.json" \
-F "active=true" \
-F "verified=false" \
-F "close_old_findings=true"
The close_old_findings=true parameter causes DefectDojo to close findings from previous scans of this engagement that are no longer present in the current results. This is essential for tracking remediated vulnerabilities accurately.
Trivy: container image scanning¶
Trivy runs as a step in every container image build pipeline in GitLab CI. The pipeline step produces a JSON report, then posts it to DefectDojo.
GitLab CI step (trivy-scan job):
trivy-scan:
stage: security
image: aquasec/trivy:0.50.0
script:
- trivy image --format json --output trivy-results.json ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}
- |
curl -X POST https://defectdojo.golemtrust.am/api/v2/import-scan/ \
-H "Authorization: Token ${DEFECTDOJO_TRIVY_TOKEN}" \
-F "scan_type=Trivy Scan" \
-F "engagement=${DEFECTDOJO_CONTAINERS_ENGAGEMENT_ID}" \
-F "file=@trivy-results.json" \
-F "active=true" \
-F "verified=false" \
-F "close_old_findings=true"
artifacts:
paths:
- trivy-results.json
expire_in: 7 days
allow_failure: false
DEFECTDOJO_TRIVY_TOKEN is stored as a protected GitLab CI variable, fetched from Vault at pipeline start. DEFECTDOJO_CONTAINERS_ENGAGEMENT_ID is the numeric ID of the Containers product engagement for continuous CI scans.
OpenVAS/Greenbone: infrastructure scanning¶
OpenVAS runs weekly scans of the Golem Trust server inventory. After each scan completes, an export script pulls the XML report via the Greenbone Management Protocol and posts it to DefectDojo.
The export and import script, run on the OpenVAS host:
#!/bin/bash
set -euo pipefail
SCAN_ID="${1}"
REPORT_FORMAT_ID="a994b278-1f62-11e1-96ac-406186ea4fc5" # OpenVAS XML format
# Export the report
gvm-cli --gmp-username admin --gmp-password "${GVM_PASSWORD}" \
socket --socketpath /run/gvmd/gvmd.sock \
--xml "<get_reports report_id=\"${SCAN_ID}\" format_id=\"${REPORT_FORMAT_ID}\"/>" \
| xmllint --xpath "//report" - > /tmp/openvas-report.xml
# Import to DefectDojo
curl -X POST https://defectdojo.golemtrust.am/api/v2/import-scan/ \
-H "Authorization: Token ${DEFECTDOJO_OPENVAS_TOKEN}" \
-F "scan_type=OpenVAS Parser" \
-F "engagement=${DEFECTDOJO_INFRASTRUCTURE_ENGAGEMENT_ID}" \
-F "file=@/tmp/openvas-report.xml" \
-F "active=true" \
-F "verified=false" \
-F "close_old_findings=true"
rm /tmp/openvas-report.xml
This script is triggered by a cron job on the OpenVAS host 30 minutes after each scheduled scan window closes, allowing time for the scan to complete. Ponder reviews the engagement monthly to ensure all server IP ranges are covered.
GitLab SAST: application source code¶
GitLab’s built-in SAST produces a gl-sast-report.json file as a CI artefact. A downstream pipeline step imports it into DefectDojo:
defectdojo-sast-import:
stage: post-security
image: curlimages/curl:8.5.0
needs:
- job: sast
artifacts: true
script:
- |
curl -X POST https://defectdojo.golemtrust.am/api/v2/import-scan/ \
-H "Authorization: Token ${DEFECTDOJO_GITLAB_SAST_TOKEN}" \
-F "scan_type=GitLab SAST Report" \
-F "engagement=${DEFECTDOJO_APPLICATIONS_ENGAGEMENT_ID}" \
-F "file=@gl-sast-report.json" \
-F "active=true" \
-F "verified=false" \
-F "close_old_findings=true"
allow_failure: false
GitLab SAST must be enabled in the project’s gitlab-ci.yml via the include: template: Security/SAST.gitlab-ci.yml directive. For the Royal Bank integration services, the findings are imported to the Royal-Bank-Integration product engagement instead.
Wazuh: host OS package CVEs¶
Wazuh’s vulnerability detection module identifies CVEs in installed OS packages. A custom integration script in /var/ossec/integrations/ runs when Wazuh generates a CVE alert and posts the finding to DefectDojo.
The integration script at /var/ossec/integrations/defectdojo-vuln.py:
import json
import sys
import requests
DEFECTDOJO_URL = "https://defectdojo.golemtrust.am/api/v2/import-scan/"
DEFECTDOJO_TOKEN = open("/var/ossec/etc/defectdojo-token").read().strip()
INFRASTRUCTURE_ENGAGEMENT_ID = 12
def post_finding(alert_data):
# Convert Wazuh alert format to Generic Findings Import JSON
finding = {
"title": alert_data.get("data", {}).get("vulnerability", {}).get("cve", "Unknown CVE"),
"severity": alert_data.get("rule", {}).get("level_label", "medium").capitalize(),
"description": json.dumps(alert_data, indent=2),
"date": alert_data.get("timestamp", "")[:10],
}
payload = {"findings": [finding]}
with open("/tmp/wazuh-finding.json", "w") as f:
json.dump(payload, f)
response = requests.post(
DEFECTDOJO_URL,
headers={"Authorization": f"Token {DEFECTDOJO_TOKEN}"},
files={
"file": open("/tmp/wazuh-finding.json", "rb"),
"scan_type": (None, "Generic Findings Import"),
"engagement": (None, str(INFRASTRUCTURE_ENGAGEMENT_ID)),
"active": (None, "true"),
"verified": (None, "false"),
},
timeout=30,
)
response.raise_for_status()
alert = json.loads(open(sys.argv[1]).read())
post_finding(alert)
The Wazuh ossec.conf integration stanza triggers this script for alerts with rule groups containing vulnerability-detector:
<integration>
<name>defectdojo-vuln</name>
<hook_url>https://defectdojo.golemtrust.am/api/v2/import-scan/</hook_url>
<rule_id>23501</rule_id>
<alert_format>json</alert_format>
</integration>
Manual penetration test findings¶
Penetration test results, whether from Dr. Crucible’s internal team or external testers, are imported via the DefectDojo UI using “Generic Findings Import” with a manually prepared JSON file, or via the API for bulk imports. The pentest-import-token is used for this purpose. Findings are always set to verified=true and reviewed before import.
Error handling and alerting¶
Every scanner integration script must log success or failure. Failures alert Cheery via Graylog.
A Graylog alert condition on the “DefectDojo Integrations” stream fires if no successful import message is received from a given scanner within its expected window:
Alert: Trivy-Import-Missing
Condition: message_count < 1
Time range: 24 hours
Filter: source:ci-runner AND message:"DefectDojo import successful" AND scanner:trivy
Similar alerts exist for OpenVAS (7-day window, matching the weekly scan schedule), Wazuh (1-hour window, since Wazuh alerts are continuous), and GitLab SAST (24-hour window).
When a scanner integration fails, the error is logged to /var/log/defectdojo-integrations/ on the relevant host and the Graylog alert notifies Cheery. She investigates and resolves before the next scan window, to avoid missing findings.