Compliance, Governance & DevSecOps Maturity
Security Dashboards & Vulnerability Management
4 min read
Visibility is the foundation of security. Without centralized dashboards and systematic vulnerability management, security teams drown in alerts while critical issues slip through. This lesson covers building effective security visibility across your DevSecOps pipeline.
Security Visibility Architecture
┌─────────────────────────────────────────────────────────┐
│ Security Visibility Stack │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ SAST │ │ SCA │ │ DAST │ │Container│ │
│ │ Results │ │ Results │ │ Results │ │ Scans │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │ │
│ └───────────┴───────────┴───────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ Aggregation Layer │ │
│ │ (DefectDojo, ASPM) │ │
│ └───────────┬───────────┘ │
│ │ │
│ ┌───────────┴───────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Dashboard │ │ Alerts │ │
│ │ (Grafana) │ │ (PagerDuty) │ │
│ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
DefectDojo: Vulnerability Management Platform
Installation
# Docker Compose deployment
git clone https://github.com/DefectDojo/django-DefectDojo.git
cd django-DefectDojo
# Start DefectDojo
docker-compose up -d
Import Scan Results
# Import via API
curl -X POST "https://defectdojo.example.com/api/v2/import-scan/" \
-H "Authorization: Token $DEFECTDOJO_TOKEN" \
-H "Content-Type: multipart/form-data" \
-F "scan_type=Trivy Scan" \
-F "file=@trivy-results.json" \
-F "product_name=MyApp" \
-F "engagement_name=CI/CD Pipeline"
GitHub Actions Integration
# .github/workflows/security-scan.yml
name: Security Scan & Report
on:
push:
branches: [main]
jobs:
scan-and-report:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy Scan
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
format: 'json'
output: 'trivy-results.json'
- name: Run Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: auto
output: semgrep-results.json
- name: Upload to DefectDojo
run: |
# Upload Trivy results
curl -X POST "${{ secrets.DEFECTDOJO_URL }}/api/v2/import-scan/" \
-H "Authorization: Token ${{ secrets.DEFECTDOJO_TOKEN }}" \
-F "scan_type=Trivy Scan" \
-F "file=@trivy-results.json" \
-F "product_name=${{ github.repository }}" \
-F "engagement_name=Pipeline-${{ github.run_id }}" \
-F "close_old_findings=true"
# Upload Semgrep results
curl -X POST "${{ secrets.DEFECTDOJO_URL }}/api/v2/import-scan/" \
-H "Authorization: Token ${{ secrets.DEFECTDOJO_TOKEN }}" \
-F "scan_type=Semgrep JSON Report" \
-F "file=@semgrep-results.json" \
-F "product_name=${{ github.repository }}" \
-F "engagement_name=Pipeline-${{ github.run_id }}"
Building Grafana Security Dashboards
Metrics Collection with Prometheus
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'security-metrics'
static_configs:
- targets: ['security-exporter:9090']
Security Metrics Exporter
# security_exporter.py
from prometheus_client import start_http_server, Gauge
import requests
import time
# Define metrics
VULNERABILITIES_TOTAL = Gauge(
'security_vulnerabilities_total',
'Total vulnerabilities by severity',
['severity', 'product']
)
SLA_COMPLIANCE = Gauge(
'security_sla_compliance_percentage',
'Percentage of vulnerabilities within SLA',
['product']
)
MTTR = Gauge(
'security_mttr_days',
'Mean time to remediate in days',
['severity', 'product']
)
def collect_metrics():
# Fetch from DefectDojo API
response = requests.get(
f"{DEFECTDOJO_URL}/api/v2/findings/",
headers={"Authorization": f"Token {DEFECTDOJO_TOKEN}"},
params={"active": "true"}
)
findings = response.json()['results']
# Count by severity
severity_counts = {}
for finding in findings:
severity = finding['severity']
product = finding['product_name']
key = (severity, product)
severity_counts[key] = severity_counts.get(key, 0) + 1
for (severity, product), count in severity_counts.items():
VULNERABILITIES_TOTAL.labels(
severity=severity,
product=product
).set(count)
if __name__ == '__main__':
start_http_server(9090)
while True:
collect_metrics()
time.sleep(60)
Grafana Dashboard JSON
{
"dashboard": {
"title": "Security Overview",
"panels": [
{
"title": "Critical Vulnerabilities",
"type": "stat",
"targets": [{
"expr": "sum(security_vulnerabilities_total{severity='Critical'})"
}],
"fieldConfig": {
"defaults": {
"thresholds": {
"steps": [
{"color": "green", "value": 0},
{"color": "yellow", "value": 1},
{"color": "red", "value": 5}
]
}
}
}
},
{
"title": "Vulnerability Trend",
"type": "timeseries",
"targets": [{
"expr": "sum(security_vulnerabilities_total) by (severity)",
"legendFormat": "{{severity}}"
}]
},
{
"title": "MTTR by Severity",
"type": "bargauge",
"targets": [{
"expr": "security_mttr_days",
"legendFormat": "{{severity}}"
}]
}
]
}
}
Vulnerability SLA Management
Defining SLAs
| Severity | Detection to Triage | Triage to Fix |
|---|---|---|
| Critical | 4 hours | 24 hours |
| High | 24 hours | 7 days |
| Medium | 3 days | 30 days |
| Low | 7 days | 90 days |
SLA Tracking Workflow
# .github/workflows/sla-check.yml
name: SLA Compliance Check
on:
schedule:
- cron: '0 9 * * *' # Daily at 9 AM
jobs:
check-sla:
runs-on: ubuntu-latest
steps:
- name: Check SLA Breaches
uses: actions/github-script@v7
with:
script: |
const response = await fetch(
`${process.env.DEFECTDOJO_URL}/api/v2/findings/?active=true`,
{
headers: {
'Authorization': `Token ${process.env.DEFECTDOJO_TOKEN}`
}
}
);
const data = await response.json();
const now = new Date();
const slaLimits = {
'Critical': 1, // 1 day
'High': 7, // 7 days
'Medium': 30, // 30 days
'Low': 90 // 90 days
};
const breaches = data.results.filter(finding => {
const created = new Date(finding.created);
const daysOpen = (now - created) / (1000 * 60 * 60 * 24);
return daysOpen > slaLimits[finding.severity];
});
if (breaches.length > 0) {
console.log(`SLA Breaches: ${breaches.length}`);
breaches.forEach(b => {
console.log(`- ${b.title} (${b.severity})`);
});
// Create issue or send alert
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `Security SLA Breach Alert: ${breaches.length} findings`,
body: breaches.map(b => `- ${b.title} (${b.severity})`).join('\n'),
labels: ['security', 'sla-breach']
});
}
env:
DEFECTDOJO_URL: ${{ secrets.DEFECTDOJO_URL }}
DEFECTDOJO_TOKEN: ${{ secrets.DEFECTDOJO_TOKEN }}
Alert Configuration
PagerDuty Integration
# alertmanager.yml
route:
receiver: 'security-team'
routes:
- match:
severity: critical
receiver: 'security-critical'
continue: true
receivers:
- name: 'security-team'
slack_configs:
- api_url: 'https://hooks.slack.com/services/xxx'
channel: '#security-alerts'
title: '{{ .CommonAnnotations.summary }}'
- name: 'security-critical'
pagerduty_configs:
- routing_key: 'your-pagerduty-key'
severity: critical
Slack Alert Formatting
# Alert template
- name: Security Vulnerability Alert
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
-H 'Content-type: application/json' \
-d '{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "🚨 Critical Vulnerability Detected"
}
},
{
"type": "section",
"fields": [
{"type": "mrkdwn", "text": "*Severity:*\nCritical"},
{"type": "mrkdwn", "text": "*Repository:*\n${{ github.repository }}"},
{"type": "mrkdwn", "text": "*CVE:*\nCVE-2024-XXXXX"},
{"type": "mrkdwn", "text": "*SLA:*\n24 hours"}
]
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {"type": "plain_text", "text": "View Details"},
"url": "https://defectdojo.example.com/finding/123"
}
]
}
]
}'
Key Security Metrics
| Metric | Description | Target |
|---|---|---|
| MTTR | Mean Time to Remediate | Critical < 24h |
| MTTD | Mean Time to Detect | < 1 day |
| Vulnerability Density | Vulns per 1000 LOC | < 1.0 |
| SLA Compliance | % within SLA | > 95% |
| False Positive Rate | Invalid findings | < 10% |
| Coverage | % of repos scanned | 100% |
Next, we'll explore the DevSecOps maturity model and building a security-first culture. :::