Overview
Trivy is a comprehensive and versatile security scanner that detects vulnerabilities in container images, file systems, Git repositories, and configuration files. This guide covers integrating Trivy into Java applications for automated security scanning in CI/CD pipelines and runtime environments.
Architecture Components
1. Trivy Java Client
@Component
public class TrivyClient {
private final ProcessBuilder processBuilder;
private final String trivyPath;
private final ObjectMapper objectMapper;
private final TrivyConfig config;
public TrivyClient(TrivyConfig config) {
this.config = config;
this.trivyPath = config.getTrivyPath();
this.processBuilder = new ProcessBuilder();
this.objectMapper = new ObjectMapper();
validateTrivyInstallation();
}
private void validateTrivyInstallation() {
try {
Process process = processBuilder.command(trivyPath, "version").start();
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IllegalStateException("Trivy is not properly installed");
}
} catch (IOException | InterruptedException e) {
throw new IllegalStateException("Failed to validate Trivy installation", e);
}
}
public ScanResult scanImage(String imageName) throws TrivyException {
return scanImage(imageName, ScanConfig.defaultConfig());
}
public ScanResult scanImage(String imageName, ScanConfig scanConfig) throws TrivyException {
try {
List<String> command = buildScanCommand(imageName, scanConfig);
Process process = processBuilder.command(command).start();
String output = captureOutput(process.getInputStream());
String error = captureOutput(process.getErrorStream());
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new TrivyException("Trivy scan failed: " + error);
}
return parseScanResult(output, imageName);
} catch (IOException | InterruptedException e) {
throw new TrivyException("Failed to execute Trivy scan", e);
}
}
public ScanResult scanFilesystem(String path) throws TrivyException {
return scanFilesystem(path, ScanConfig.defaultConfig());
}
public ScanResult scanFilesystem(String path, ScanConfig scanConfig) throws TrivyException {
try {
List<String> command = buildFilesystemScanCommand(path, scanConfig);
Process process = processBuilder.command(command).start();
String output = captureOutput(process.getInputStream());
String error = captureOutput(process.getErrorStream());
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new TrivyException("Trivy filesystem scan failed: " + error);
}
return parseScanResult(output, "filesystem:" + path);
} catch (IOException | InterruptedException e) {
throw new TrivyException("Failed to execute Trivy filesystem scan", e);
}
}
public List<Vulnerability> scanForVulnerabilities(String target, ScanType scanType)
throws TrivyException {
ScanConfig config = ScanConfig.builder()
.format("json")
.severityThreshold(config.getSeverityThreshold())
.build();
ScanResult result;
switch (scanType) {
case IMAGE:
result = scanImage(target, config);
break;
case FILESYSTEM:
result = scanFilesystem(target, config);
break;
case REPOSITORY:
result = scanRepository(target, config);
break;
default:
throw new IllegalArgumentException("Unsupported scan type: " + scanType);
}
return result.getVulnerabilities();
}
private List<String> buildScanCommand(String target, ScanConfig scanConfig) {
List<String> command = new ArrayList<>();
command.add(trivyPath);
command.add("image");
// Add format
command.add("--format");
command.add(scanConfig.getFormat());
// Add severity filter
if (scanConfig.getSeverityThreshold() != null) {
command.add("--severity");
command.add(scanConfig.getSeverityThreshold().name());
}
// Add timeout
if (scanConfig.getTimeout() != null) {
command.add("--timeout");
command.add(scanConfig.getTimeout().toString());
}
// Skip update if configured
if (scanConfig.isSkipUpdate()) {
command.add("--skip-update");
}
// Add output file if specified
if (scanConfig.getOutputFile() != null) {
command.add("--output");
command.add(scanConfig.getOutputFile());
}
// Add ignore file if specified
if (scanConfig.getIgnoreFile() != null) {
command.add("--ignorefile");
command.add(scanConfig.getIgnoreFile());
}
// Add ignore unfixed if configured
if (scanConfig.isIgnoreUnfixed()) {
command.add("--ignore-unfixed");
}
// Add remote server if configured
if (config.getRemoteServer() != null) {
command.add("--remote");
command.add(config.getRemoteServer());
}
// Add target
command.add(target);
return command;
}
private List<String> buildFilesystemScanCommand(String path, ScanConfig scanConfig) {
List<String> command = new ArrayList<>();
command.add(trivyPath);
command.add("fs");
command.add("--format");
command.add(scanConfig.getFormat());
command.add(path);
return command;
}
private ScanResult scanRepository(String repositoryUrl, ScanConfig scanConfig)
throws TrivyException {
// Implementation for Git repository scanning
throw new UnsupportedOperationException("Repository scanning not yet implemented");
}
private String captureOutput(InputStream inputStream) throws IOException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
return reader.lines().collect(Collectors.joining("\n"));
}
}
private ScanResult parseScanResult(String jsonOutput, String target) throws TrivyException {
try {
JsonNode rootNode = objectMapper.readTree(jsonOutput);
List<Vulnerability> vulnerabilities = new ArrayList<>();
if (rootNode.has("Results")) {
for (JsonNode resultNode : rootNode.path("Results")) {
if (resultNode.has("Vulnerabilities")) {
for (JsonNode vulnNode : resultNode.path("Vulnerabilities")) {
Vulnerability vulnerability = parseVulnerability(vulnNode);
vulnerabilities.add(vulnerability);
}
}
}
}
return new ScanResult(target, vulnerabilities, Instant.now());
} catch (JsonProcessingException e) {
throw new TrivyException("Failed to parse Trivy scan result", e);
}
}
private Vulnerability parseVulnerability(JsonNode vulnNode) {
return Vulnerability.builder()
.vulnerabilityId(vulnNode.path("VulnerabilityID").asText())
.pkgName(vulnNode.path("PkgName").asText())
.installedVersion(vulnNode.path("InstalledVersion").asText())
.fixedVersion(vulnNode.path("FixedVersion").asText())
.severity(Severity.fromString(vulnNode.path("Severity").asText()))
.title(vulnNode.path("Title").asText())
.description(vulnNode.path("Description").asText())
.references(parseReferences(vulnNode.path("References")))
.cvssScore(parseCvssScore(vulnNode))
.build();
}
private List<String> parseReferences(JsonNode referencesNode) {
List<String> references = new ArrayList<>();
if (referencesNode.isArray()) {
for (JsonNode refNode : referencesNode) {
references.add(refNode.asText());
}
}
return references;
}
private Double parseCvssScore(JsonNode vulnNode) {
if (vulnNode.has("CVSS") && vulnNode.path("CVSS").has("nvd") &&
vulnNode.path("CVSS").path("nvd").has("V3Score")) {
return vulnNode.path("CVSS").path("nvd").path("V3Score").asDouble();
}
return null;
}
}
2. Configuration Management
@ConfigurationProperties(prefix = "trivy")
public class TrivyConfig {
private String trivyPath = "trivy";
private String cacheDir;
private String remoteServer;
private Severity severityThreshold = Severity.HIGH;
private Duration timeout = Duration.ofMinutes(10);
private boolean skipUpdate = false;
private ReportConfig report = new ReportConfig();
private DatabaseConfig database = new DatabaseConfig();
// Getters and setters
public String getTrivyPath() { return trivyPath; }
public void setTrivyPath(String trivyPath) { this.trivyPath = trivyPath; }
public String getCacheDir() { return cacheDir; }
public void setCacheDir(String cacheDir) { this.cacheDir = cacheDir; }
public String getRemoteServer() { return remoteServer; }
public void setRemoteServer(String remoteServer) { this.remoteServer = remoteServer; }
public Severity getSeverityThreshold() { return severityThreshold; }
public void setSeverityThreshold(Severity severityThreshold) {
this.severityThreshold = severityThreshold;
}
public Duration getTimeout() { return timeout; }
public void setTimeout(Duration timeout) { this.timeout = timeout; }
public boolean isSkipUpdate() { return skipUpdate; }
public void setSkipUpdate(boolean skipUpdate) { this.skipUpdate = skipUpdate; }
public ReportConfig getReport() { return report; }
public void setReport(ReportConfig report) { this.report = report; }
public DatabaseConfig getDatabase() { return database; }
public void setDatabase(DatabaseConfig database) { this.database = database; }
public static class ReportConfig {
private String format = "table";
private String outputDir = "./reports";
private boolean generateHtml = false;
private boolean generateJson = true;
// Getters and setters
}
public static class DatabaseConfig {
private Duration updateInterval = Duration.ofHours(24);
private boolean autoUpdate = true;
private String mirrorUrl;
// Getters and setters
}
}
public class ScanConfig {
private String format = "json";
private Severity severityThreshold;
private Duration timeout;
private boolean skipUpdate = false;
private String outputFile;
private String ignoreFile;
private boolean ignoreUnfixed = false;
private ScanConfig(Builder builder) {
this.format = builder.format;
this.severityThreshold = builder.severityThreshold;
this.timeout = builder.timeout;
this.skipUpdate = builder.skipUpdate;
this.outputFile = builder.outputFile;
this.ignoreFile = builder.ignoreFile;
this.ignoreUnfixed = builder.ignoreUnfixed;
}
public static ScanConfig defaultConfig() {
return builder().build();
}
public static Builder builder() {
return new Builder();
}
// Getters
public String getFormat() { return format; }
public Severity getSeverityThreshold() { return severityThreshold; }
public Duration getTimeout() { return timeout; }
public boolean isSkipUpdate() { return skipUpdate; }
public String getOutputFile() { return outputFile; }
public String getIgnoreFile() { return ignoreFile; }
public boolean isIgnoreUnfixed() { return ignoreUnfixed; }
public static class Builder {
private String format = "json";
private Severity severityThreshold;
private Duration timeout;
private boolean skipUpdate = false;
private String outputFile;
private String ignoreFile;
private boolean ignoreUnfixed = false;
public Builder format(String format) {
this.format = format;
return this;
}
public Builder severityThreshold(Severity severityThreshold) {
this.severityThreshold = severityThreshold;
return this;
}
public Builder timeout(Duration timeout) {
this.timeout = timeout;
return this;
}
public Builder skipUpdate(boolean skipUpdate) {
this.skipUpdate = skipUpdate;
return this;
}
public Builder outputFile(String outputFile) {
this.outputFile = outputFile;
return this;
}
public Builder ignoreFile(String ignoreFile) {
this.ignoreFile = ignoreFile;
return this;
}
public Builder ignoreUnfixed(boolean ignoreUnfixed) {
this.ignoreUnfixed = ignoreUnfixed;
return this;
}
public ScanConfig build() {
return new ScanConfig(this);
}
}
}
Vulnerability Management
1. Vulnerability Data Model
public class ScanResult {
private final String target;
private final List<Vulnerability> vulnerabilities;
private final Instant scanTime;
private final ScanSummary summary;
public ScanResult(String target, List<Vulnerability> vulnerabilities, Instant scanTime) {
this.target = target;
this.vulnerabilities = vulnerabilities != null ? vulnerabilities : new ArrayList<>();
this.scanTime = scanTime;
this.summary = calculateSummary();
}
private ScanSummary calculateSummary() {
Map<Severity, Long> severityCounts = vulnerabilities.stream()
.collect(Collectors.groupingBy(Vulnerability::getSeverity, Collectors.counting()));
long total = vulnerabilities.size();
long fixable = vulnerabilities.stream()
.filter(v -> v.getFixedVersion() != null && !v.getFixedVersion().isEmpty())
.count();
double averageCvss = vulnerabilities.stream()
.mapToDouble(v -> v.getCvssScore() != null ? v.getCvssScore() : 0.0)
.average()
.orElse(0.0);
return new ScanSummary(total, fixable, averageCvss, severityCounts);
}
public boolean hasVulnerabilities() {
return !vulnerabilities.isEmpty();
}
public boolean hasCriticalVulnerabilities() {
return vulnerabilities.stream()
.anyMatch(v -> v.getSeverity() == Severity.CRITICAL);
}
public List<Vulnerability> getVulnerabilitiesBySeverity(Severity severity) {
return vulnerabilities.stream()
.filter(v -> v.getSeverity() == severity)
.collect(Collectors.toList());
}
public List<Vulnerability> getFixableVulnerabilities() {
return vulnerabilities.stream()
.filter(v -> v.getFixedVersion() != null && !v.getFixedVersion().isEmpty())
.collect(Collectors.toList());
}
// Getters
public String getTarget() { return target; }
public List<Vulnerability> getVulnerabilities() { return vulnerabilities; }
public Instant getScanTime() { return scanTime; }
public ScanSummary getSummary() { return summary; }
}
public class Vulnerability {
private final String vulnerabilityId;
private final String pkgName;
private final String installedVersion;
private final String fixedVersion;
private final Severity severity;
private final String title;
private final String description;
private final List<String> references;
private final Double cvssScore;
private final Instant publishedDate;
private final Instant lastModifiedDate;
private Vulnerability(Builder builder) {
this.vulnerabilityId = builder.vulnerabilityId;
this.pkgName = builder.pkgName;
this.installedVersion = builder.installedVersion;
this.fixedVersion = builder.fixedVersion;
this.severity = builder.severity;
this.title = builder.title;
this.description = builder.description;
this.references = builder.references;
this.cvssScore = builder.cvssScore;
this.publishedDate = builder.publishedDate;
this.lastModifiedDate = builder.lastModifiedDate;
}
public static Builder builder() {
return new Builder();
}
// Getters
public String getVulnerabilityId() { return vulnerabilityId; }
public String getPkgName() { return pkgName; }
public String getInstalledVersion() { return installedVersion; }
public String getFixedVersion() { return fixedVersion; }
public Severity getSeverity() { return severity; }
public String getTitle() { return title; }
public String getDescription() { return description; }
public List<String> getReferences() { return references; }
public Double getCvssScore() { return cvssScore; }
public Instant getPublishedDate() { return publishedDate; }
public Instant getLastModifiedDate() { return lastModifiedDate; }
public boolean isFixable() {
return fixedVersion != null && !fixedVersion.isEmpty();
}
public static class Builder {
private String vulnerabilityId;
private String pkgName;
private String installedVersion;
private String fixedVersion;
private Severity severity;
private String title;
private String description;
private List<String> references = new ArrayList<>();
private Double cvssScore;
private Instant publishedDate;
private Instant lastModifiedDate;
public Builder vulnerabilityId(String vulnerabilityId) {
this.vulnerabilityId = vulnerabilityId;
return this;
}
public Builder pkgName(String pkgName) {
this.pkgName = pkgName;
return this;
}
public Builder installedVersion(String installedVersion) {
this.installedVersion = installedVersion;
return this;
}
public Builder fixedVersion(String fixedVersion) {
this.fixedVersion = fixedVersion;
return this;
}
public Builder severity(Severity severity) {
this.severity = severity;
return this;
}
public Builder title(String title) {
this.title = title;
return this;
}
public Builder description(String description) {
this.description = description;
return this;
}
public Builder references(List<String> references) {
this.references = references;
return this;
}
public Builder cvssScore(Double cvssScore) {
this.cvssScore = cvssScore;
return this;
}
public Builder publishedDate(Instant publishedDate) {
this.publishedDate = publishedDate;
return this;
}
public Builder lastModifiedDate(Instant lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
return this;
}
public Vulnerability build() {
return new Vulnerability(this);
}
}
}
public enum Severity {
UNKNOWN(0),
LOW(1),
MEDIUM(2),
HIGH(3),
CRITICAL(4);
private final int level;
Severity(int level) {
this.level = level;
}
public int getLevel() {
return level;
}
public boolean isAtLeast(Severity other) {
return this.level >= other.level;
}
public static Severity fromString(String severity) {
if (severity == null) return UNKNOWN;
switch (severity.toUpperCase()) {
case "LOW": return LOW;
case "MEDIUM": return MEDIUM;
case "HIGH": return HIGH;
case "CRITICAL": return CRITICAL;
default: return UNKNOWN;
}
}
}
public class ScanSummary {
private final long totalVulnerabilities;
private final long fixableVulnerabilities;
private final double averageCvssScore;
private final Map<Severity, Long> severityCounts;
public ScanSummary(long totalVulnerabilities, long fixableVulnerabilities,
double averageCvssScore, Map<Severity, Long> severityCounts) {
this.totalVulnerabilities = totalVulnerabilities;
this.fixableVulnerabilities = fixableVulnerabilities;
this.averageCvssScore = averageCvssScore;
this.severityCounts = new EnumMap<>(severityCounts);
}
public long getCountBySeverity(Severity severity) {
return severityCounts.getOrDefault(severity, 0L);
}
public boolean hasVulnerabilities() {
return totalVulnerabilities > 0;
}
public boolean hasSevereVulnerabilities() {
return getCountBySeverity(Severity.HIGH) > 0 ||
getCountBySeverity(Severity.CRITICAL) > 0;
}
// Getters
public long getTotalVulnerabilities() { return totalVulnerabilities; }
public long getFixableVulnerabilities() { return fixableVulnerabilities; }
public double getAverageCvssScore() { return averageCvssScore; }
public Map<Severity, Long> getSeverityCounts() { return severityCounts; }
}
public enum ScanType {
IMAGE,
FILESYSTEM,
REPOSITORY,
CONFIG
}
2. Vulnerability Analysis Service
@Service
public class VulnerabilityAnalysisService {
private final TrivyClient trivyClient;
private final VulnerabilityRepository vulnerabilityRepository;
private final NotificationService notificationService;
public VulnerabilityAnalysisService(TrivyClient trivyClient,
VulnerabilityRepository vulnerabilityRepository,
NotificationService notificationService) {
this.trivyClient = trivyClient;
this.vulnerabilityRepository = vulnerabilityRepository;
this.notificationService = notificationService;
}
public ScanResult analyzeImage(String imageName) throws TrivyException {
return analyzeImage(imageName, ScanConfig.defaultConfig());
}
public ScanResult analyzeImage(String imageName, ScanConfig scanConfig) throws TrivyException {
ScanResult result = trivyClient.scanImage(imageName, scanConfig);
// Store result
vulnerabilityRepository.saveScanResult(result);
// Check for policy violations
checkPolicyViolations(result);
return result;
}
public ComplianceReport checkCompliance(ScanResult result, CompliancePolicy policy) {
List<ComplianceViolation> violations = new ArrayList<>();
// Check severity thresholds
if (result.getSummary().getCountBySeverity(Severity.CRITICAL) > policy.getMaxCritical()) {
violations.add(new ComplianceViolation(
"CRITICAL_SEVERITY_THRESHOLD",
String.format("Exceeded maximum critical vulnerabilities: %d > %d",
result.getSummary().getCountBySeverity(Severity.CRITICAL),
policy.getMaxCritical())
));
}
if (result.getSummary().getCountBySeverity(Severity.HIGH) > policy.getMaxHigh()) {
violations.add(new ComplianceViolation(
"HIGH_SEVERITY_THRESHOLD",
String.format("Exceeded maximum high vulnerabilities: %d > %d",
result.getSummary().getCountBySeverity(Severity.HIGH),
policy.getMaxHigh())
));
}
// Check CVSS threshold
if (result.getSummary().getAverageCvssScore() > policy.getMaxCvssScore()) {
violations.add(new ComplianceViolation(
"CVSS_THRESHOLD",
String.format("Average CVSS score exceeds threshold: %.2f > %.2f",
result.getSummary().getAverageCvssScore(),
policy.getMaxCvssScore())
));
}
// Check specific vulnerability bans
for (Vulnerability vulnerability : result.getVulnerabilities()) {
if (policy.getBannedVulnerabilities().contains(vulnerability.getVulnerabilityId())) {
violations.add(new ComplianceViolation(
"BANNED_VULNERABILITY",
String.format("Banned vulnerability detected: %s",
vulnerability.getVulnerabilityId())
));
}
}
boolean compliant = violations.isEmpty();
return new ComplianceReport(compliant, violations, result.getSummary());
}
public TrendAnalysis analyzeTrends(String imageName, Duration period) {
List<ScanResult> historicalScans = vulnerabilityRepository
.findScanResultsByTargetAndPeriod(imageName, period);
if (historicalScans.isEmpty()) {
return new TrendAnalysis(imageName, period, Collections.emptyMap(),
Trend.UNKNOWN, 0.0);
}
// Calculate trend metrics
Map<Severity, Double> severityTrends = calculateSeverityTrends(historicalScans);
Trend overallTrend = calculateOverallTrend(historicalScans);
double improvementRate = calculateImprovementRate(historicalScans);
return new TrendAnalysis(imageName, period, severityTrends, overallTrend, improvementRate);
}
public List<Vulnerability> getFixRecommendations(ScanResult result) {
return result.getVulnerabilities().stream()
.filter(Vulnerability::isFixable)
.sorted(Comparator.comparing(Vulnerability::getSeverity).reversed()
.thenComparing(Vulnerability::getCvssScore, Comparator.reverseOrder()))
.collect(Collectors.toList());
}
public ImpactAssessment assessImpact(ScanResult result, String environment) {
Map<Severity, Double> riskScores = Map.of(
Severity.CRITICAL, 10.0,
Severity.HIGH, 7.5,
Severity.MEDIUM, 5.0,
Severity.LOW, 2.5,
Severity.UNKNOWN, 1.0
);
double totalRiskScore = result.getVulnerabilities().stream()
.mapToDouble(v -> riskScores.getOrDefault(v.getSeverity(), 1.0))
.sum();
double normalizedScore = totalRiskScore / Math.max(1, result.getVulnerabilities().size());
RiskLevel riskLevel;
if (normalizedScore >= 8.0) riskLevel = RiskLevel.CRITICAL;
else if (normalizedScore >= 6.0) riskLevel = RiskLevel.HIGH;
else if (normalizedScore >= 4.0) riskLevel = RiskLevel.MEDIUM;
else if (normalizedScore >= 2.0) riskLevel = RiskLevel.LOW;
else riskLevel = RiskLevel.NONE;
return new ImpactAssessment(result.getTarget(), environment, riskLevel,
normalizedScore, result.getSummary());
}
private void checkPolicyViolations(ScanResult result) {
// Implement policy violation checks and notifications
if (result.hasCriticalVulnerabilities()) {
notificationService.sendCriticalAlert(result);
}
}
private Map<Severity, Double> calculateSeverityTrends(List<ScanResult> historicalScans) {
Map<Severity, Double> trends = new EnumMap<>(Severity.class);
for (Severity severity : Severity.values()) {
if (historicalScans.size() < 2) {
trends.put(severity, 0.0);
continue;
}
ScanResult oldest = historicalScans.get(0);
ScanResult newest = historicalScans.get(historicalScans.size() - 1);
long oldCount = oldest.getSummary().getCountBySeverity(severity);
long newCount = newest.getSummary().getCountBySeverity(severity);
double trend = oldCount == 0 ?
(newCount > 0 ? 100.0 : 0.0) :
((double) (newCount - oldCount) / oldCount) * 100;
trends.put(severity, trend);
}
return trends;
}
private Trend calculateOverallTrend(List<ScanResult> historicalScans) {
if (historicalScans.size() < 2) return Trend.UNKNOWN;
ScanResult oldest = historicalScans.get(0);
ScanResult newest = historicalScans.get(historicalScans.size() - 1);
long oldTotal = oldest.getSummary().getTotalVulnerabilities();
long newTotal = newest.getSummary().getTotalVulnerabilities();
if (newTotal < oldTotal) return Trend.IMPROVING;
else if (newTotal > oldTotal) return Trend.DETERIORATING;
else return Trend.STABLE;
}
private double calculateImprovementRate(List<ScanResult> historicalScans) {
if (historicalScans.size() < 2) return 0.0;
long totalFixableReduction = 0;
for (int i = 1; i < historicalScans.size(); i++) {
ScanResult previous = historicalScans.get(i - 1);
ScanResult current = historicalScans.get(i);
long previousFixable = previous.getSummary().getFixableVulnerabilities();
long currentFixable = current.getSummary().getFixableVulnerabilities();
totalFixableReduction += Math.max(0, previousFixable - currentFixable);
}
ScanResult first = historicalScans.get(0);
long initialFixable = first.getSummary().getFixableVulnerabilities();
return initialFixable > 0 ?
(double) totalFixableReduction / initialFixable * 100 : 0.0;
}
}
// Compliance and Trend Analysis Classes
public class CompliancePolicy {
private final int maxCritical;
private final int maxHigh;
private final int maxMedium;
private final double maxCvssScore;
private final Set<String> bannedVulnerabilities;
public CompliancePolicy(int maxCritical, int maxHigh, int maxMedium,
double maxCvssScore, Set<String> bannedVulnerabilities) {
this.maxCritical = maxCritical;
this.maxHigh = maxHigh;
this.maxMedium = maxMedium;
this.maxCvssScore = maxCvssScore;
this.bannedVulnerabilities = bannedVulnerabilities;
}
// Getters
public int getMaxCritical() { return maxCritical; }
public int getMaxHigh() { return maxHigh; }
public int getMaxMedium() { return maxMedium; }
public double getMaxCvssScore() { return maxCvssScore; }
public Set<String> getBannedVulnerabilities() { return bannedVulnerabilities; }
}
public class ComplianceReport {
private final boolean compliant;
private final List<ComplianceViolation> violations;
private final ScanSummary summary;
public ComplianceReport(boolean compliant, List<ComplianceViolation> violations,
ScanSummary summary) {
this.compliant = compliant;
this.violations = violations;
this.summary = summary;
}
// Getters
public boolean isCompliant() { return compliant; }
public List<ComplianceViolation> getViolations() { return violations; }
public ScanSummary getSummary() { return summary; }
}
public class ComplianceViolation {
private final String ruleId;
private final String description;
public ComplianceViolation(String ruleId, String description) {
this.ruleId = ruleId;
this.description = description;
}
// Getters
public String getRuleId() { return ruleId; }
public String getDescription() { return description; }
}
public class TrendAnalysis {
private final String target;
private final Duration period;
private final Map<Severity, Double> severityTrends;
private final Trend overallTrend;
private final double improvementRate;
public TrendAnalysis(String target, Duration period, Map<Severity, Double> severityTrends,
Trend overallTrend, double improvementRate) {
this.target = target;
this.period = period;
this.severityTrends = severityTrends;
this.overallTrend = overallTrend;
this.improvementRate = improvementRate;
}
// Getters
public String getTarget() { return target; }
public Duration getPeriod() { return period; }
public Map<Severity, Double> getSeverityTrends() { return severityTrends; }
public Trend getOverallTrend() { return overallTrend; }
public double getImprovementRate() { return improvementRate; }
}
public enum Trend {
IMPROVING,
DETERIORATING,
STABLE,
UNKNOWN
}
public class ImpactAssessment {
private final String target;
private final String environment;
private final RiskLevel riskLevel;
private final double riskScore;
private final ScanSummary summary;
public ImpactAssessment(String target, String environment, RiskLevel riskLevel,
double riskScore, ScanSummary summary) {
this.target = target;
this.environment = environment;
this.riskLevel = riskLevel;
this.riskScore = riskScore;
this.summary = summary;
}
// Getters
public String getTarget() { return target; }
public String getEnvironment() { return environment; }
public RiskLevel getRiskLevel() { return riskLevel; }
public double getRiskScore() { return riskScore; }
public ScanSummary getSummary() { return summary; }
}
public enum RiskLevel {
NONE,
LOW,
MEDIUM,
HIGH,
CRITICAL
}
CI/CD Integration
1. Jenkins Pipeline Integration
@Component
public class JenkinsTrivyIntegration {
private final TrivyClient trivyClient;
private final VulnerabilityAnalysisService analysisService;
private final ReportGenerator reportGenerator;
public JenkinsTrivyIntegration(TrivyClient trivyClient,
VulnerabilityAnalysisService analysisService,
ReportGenerator reportGenerator) {
this.trivyClient = trivyClient;
this.analysisService = analysisService;
this.reportGenerator = reportGenerator;
}
public PipelineScanResult scanInPipeline(String imageName, String buildId,
CompliancePolicy policy) {
try {
// Perform scan
ScanConfig scanConfig = ScanConfig.builder()
.format("json")
.severityThreshold(Severity.LOW)
.outputFile("./reports/trivy-scan-" + buildId + ".json")
.build();
ScanResult scanResult = trivyClient.scanImage(imageName, scanConfig);
// Check compliance
ComplianceReport complianceReport = analysisService.checkCompliance(scanResult, policy);
// Generate reports
String htmlReport = reportGenerator.generateHtmlReport(scanResult, complianceReport);
String jsonReport = reportGenerator.generateJsonReport(scanResult, complianceReport);
// Determine pipeline outcome
boolean shouldFail = !complianceReport.isCompliant();
String pipelineStatus = shouldFail ? "UNSTABLE" : "SUCCESS";
return new PipelineScanResult(
buildId,
imageName,
scanResult,
complianceReport,
pipelineStatus,
shouldFail,
htmlReport,
jsonReport
);
} catch (TrivyException e) {
return PipelineScanResult.failed(buildId, imageName, e.getMessage());
}
}
public void updateBuildStatus(String buildUrl, PipelineScanResult result) {
// Update Jenkins build status via REST API
// Implementation depends on Jenkins API
}
public void sendQualityGateResults(PipelineScanResult result) {
// Send results to quality gate systems
// Implementation depends on your quality gate setup
}
}
public class PipelineScanResult {
private final String buildId;
private final String imageName;
private final ScanResult scanResult;
private final ComplianceReport complianceReport;
private final String pipelineStatus;
private final boolean shouldFail;
private final String htmlReport;
private final String jsonReport;
private final String error;
public PipelineScanResult(String buildId, String imageName, ScanResult scanResult,
ComplianceReport complianceReport, String pipelineStatus,
boolean shouldFail, String htmlReport, String jsonReport) {
this.buildId = buildId;
this.imageName = imageName;
this.scanResult = scanResult;
this.complianceReport = complianceReport;
this.pipelineStatus = pipelineStatus;
this.shouldFail = shouldFail;
this.htmlReport = htmlReport;
this.jsonReport = jsonReport;
this.error = null;
}
public static PipelineScanResult failed(String buildId, String imageName, String error) {
PipelineScanResult result = new PipelineScanResult();
result.buildId = buildId;
result.imageName = imageName;
result.error = error;
result.pipelineStatus = "FAILED";
result.shouldFail = true;
return result;
}
private PipelineScanResult() {
// For failed result construction
this.scanResult = null;
this.complianceReport = null;
this.htmlReport = null;
this.jsonReport = null;
}
// Getters
public boolean isSuccess() { return error == null; }
public String getError() { return error; }
// ... other getters
}
2. GitHub Actions Integration
@Component
public class GitHubActionsTrivyIntegration {
private final TrivyClient trivyClient;
private final VulnerabilityAnalysisService analysisService;
public GitHubActionsTrivyIntegration(TrivyClient trivyClient,
VulnerabilityAnalysisService analysisService) {
this.trivyClient = trivyClient;
this.analysisService = analysisService;
}
public GitHubScanResult scanInWorkflow(String imageName, String workflowRunId,
CompliancePolicy policy) {
try {
ScanResult scanResult = trivyClient.scanImage(imageName);
ComplianceReport complianceReport = analysisService.checkCompliance(scanResult, policy);
// Generate GitHub Actions annotations
List<GitHubAnnotation> annotations = generateAnnotations(scanResult);
return new GitHubScanResult(
workflowRunId,
imageName,
scanResult,
complianceReport,
annotations,
complianceReport.isCompliant()
);
} catch (TrivyException e) {
return GitHubScanResult.failed(workflowRunId, imageName, e.getMessage());
}
}
private List<GitHubAnnotation> generateAnnotations(ScanResult scanResult) {
return scanResult.getVulnerabilities().stream()
.filter(v -> v.getSeverity().isAtLeast(Severity.HIGH))
.map(this::createAnnotation)
.collect(Collectors.toList());
}
private GitHubAnnotation createAnnotation(Vulnerability vulnerability) {
return new GitHubAnnotation(
"warning",
vulnerability.getTitle(),
vulnerability.getVulnerabilityId(),
vulnerability.getSeverity().name().toLowerCase(),
String.format("Package: %s %s%nFixed in: %s",
vulnerability.getPkgName(),
vulnerability.getInstalledVersion(),
vulnerability.getFixedVersion() != null ?
vulnerability.getFixedVersion() : "Not available")
);
}
public void createSecurityAdvisory(String repository, GitHubScanResult result) {
// Create GitHub Security Advisory for critical vulnerabilities
// Implementation depends on GitHub API
}
}
public class GitHubScanResult {
private final String workflowRunId;
private final String imageName;
private final ScanResult scanResult;
private final ComplianceReport complianceReport;
private final List<GitHubAnnotation> annotations;
private final boolean passed;
private final String error;
public GitHubScanResult(String workflowRunId, String imageName, ScanResult scanResult,
ComplianceReport complianceReport, List<GitHubAnnotation> annotations,
boolean passed) {
this.workflowRunId = workflowRunId;
this.imageName = imageName;
this.scanResult = scanResult;
this.complianceReport = complianceReport;
this.annotations = annotations;
this.passed = passed;
this.error = null;
}
public static GitHubScanResult failed(String workflowRunId, String imageName, String error) {
GitHubScanResult result = new GitHubScanResult();
result.workflowRunId = workflowRunId;
result.imageName = imageName;
result.error = error;
result.passed = false;
return result;
}
private GitHubScanResult() {
// For failed result construction
this.scanResult = null;
this.complianceReport = null;
this.annotations = Collections.emptyList();
}
// Getters
public boolean isSuccess() { return error == null; }
public String getError() { return error; }
// ... other getters
}
public class GitHubAnnotation {
private final String annotationLevel;
private final String message;
private final String title;
private final String rawDetails;
private final String path;
public GitHubAnnotation(String annotationLevel, String message, String title,
String rawDetails, String path) {
this.annotationLevel = annotationLevel;
this.message = message;
this.title = title;
this.rawDetails = rawDetails;
this.path = path;
}
// Getters
}
Reporting and Visualization
1. Report Generation Service
@Service
public class ReportGenerator {
private final ObjectMapper objectMapper;
private final TemplateEngine templateEngine;
public ReportGenerator() {
this.objectMapper = new ObjectMapper();
this.templateEngine = createTemplateEngine();
}
public String generateHtmlReport(ScanResult scanResult, ComplianceReport complianceReport) {
Map<String, Object> model = new HashMap<>();
model.put("scanResult", scanResult);
model.put("complianceReport", complianceReport);
model.put("scanTime", scanResult.getScanTime());
model.put("summary", scanResult.getSummary());
return templateEngine.process("security-report", new Context(Locale.getDefault(), model));
}
public String generateJsonReport(ScanResult scanResult, ComplianceReport complianceReport) {
try {
Map<String, Object> report = new HashMap<>();
report.put("scanResult", scanResult);
report.put("complianceReport", complianceReport);
report.put("generatedAt", Instant.now());
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(report);
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to generate JSON report", e);
}
}
public String generateCsvReport(ScanResult scanResult) {
StringBuilder csv = new StringBuilder();
csv.append("VulnerabilityID,Package,InstalledVersion,FixedVersion,Severity,CVSS,Title\n");
for (Vulnerability vulnerability : scanResult.getVulnerabilities()) {
csv.append(String.format("\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",%.1f,\"%s\"\n",
vulnerability.getVulnerabilityId(),
vulnerability.getPkgName(),
vulnerability.getInstalledVersion(),
vulnerability.getFixedVersion() != null ? vulnerability.getFixedVersion() : "",
vulnerability.getSeverity().name(),
vulnerability.getCvssScore() != null ? vulnerability.getCvssScore() : 0.0,
vulnerability.getTitle().replace("\"", "\"\"")
));
}
return csv.toString();
}
public byte[] generatePdfReport(ScanResult scanResult, ComplianceReport complianceReport) {
// Implement PDF generation using libraries like iText or Apache PDFBox
// This is a simplified placeholder
try {
String htmlContent = generateHtmlReport(scanResult, complianceReport);
// Convert HTML to PDF
return htmlContent.getBytes(); // Placeholder
} catch (Exception e) {
throw new RuntimeException("Failed to generate PDF report", e);
}
}
public DashboardData generateDashboardData(List<ScanResult> recentScans) {
DashboardData dashboard = new DashboardData();
// Calculate metrics
long totalScans = recentScans.size();
long failedScans = recentScans.stream()
.filter(scan -> scan.getSummary().hasSevereVulnerabilities())
.count();
double complianceRate = totalScans > 0 ?
(double) (totalScans - failedScans) / totalScans * 100 : 0.0;
// Severity distribution
Map<Severity, Long> severityDistribution = recentScans.stream()
.flatMap(scan -> scan.getVulnerabilities().stream())
.collect(Collectors.groupingBy(Vulnerability::getSeverity, Collectors.counting()));
// Top vulnerable images
Map<String, Long> vulnerableImages = recentScans.stream()
.collect(Collectors.toMap(
ScanResult::getTarget,
scan -> scan.getSummary().getTotalVulnerabilities()
));
dashboard.setTotalScans(totalScans);
dashboard.setFailedScans(failedScans);
dashboard.setComplianceRate(complianceRate);
dashboard.setSeverityDistribution(severityDistribution);
dashboard.setVulnerableImages(vulnerableImages);
dashboard.setGeneratedAt(Instant.now());
return dashboard;
}
private TemplateEngine createTemplateEngine() {
TemplateEngine templateEngine = new TemplateEngine();
// Configure template engine (Thymeleaf, Freemarker, etc.)
return templateEngine;
}
}
public class DashboardData {
private long totalScans;
private long failedScans;
private double complianceRate;
private Map<Severity, Long> severityDistribution;
private Map<String, Long> vulnerableImages;
private Instant generatedAt;
// Getters and setters
public long getTotalScans() { return totalScans; }
public void setTotalScans(long totalScans) { this.totalScans = totalScans; }
public long getFailedScans() { return failedScans; }
public void setFailedScans(long failedScans) { this.failedScans = failedScans; }
public double getComplianceRate() { return complianceRate; }
public void setComplianceRate(double complianceRate) { this.complianceRate = complianceRate; }
public Map<Severity, Long> getSeverityDistribution() { return severityDistribution; }
public void setSeverityDistribution(Map<Severity, Long> severityDistribution) {
this.severityDistribution = severityDistribution;
}
public Map<String, Long> getVulnerableImages() { return vulnerableImages; }
public void setVulnerableImages(Map<String, Long> vulnerableImages) {
this.vulnerableImages = vulnerableImages;
}
public Instant getGeneratedAt() { return generatedAt; }
public void setGeneratedAt(Instant generatedAt) { this.generatedAt = generatedAt; }
}
Spring Boot Integration
1. Auto-Configuration
@Configuration
@EnableConfigurationProperties(TrivyConfig.class)
public class TrivyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public TrivyClient trivyClient(TrivyConfig config) {
return new TrivyClient(config);
}
@Bean
@ConditionalOnMissingBean
public VulnerabilityAnalysisService vulnerabilityAnalysisService(TrivyClient trivyClient) {
return new VulnerabilityAnalysisService(
trivyClient,
new InMemoryVulnerabilityRepository(),
new NoOpNotificationService()
);
}
@Bean
@ConditionalOnMissingBean
public ReportGenerator reportGenerator() {
return new ReportGenerator();
}
@Bean
@ConditionalOnMissingBean
public JenkinsTrivyIntegration jenkinsTrivyIntegration(TrivyClient trivyClient,
VulnerabilityAnalysisService analysisService,
ReportGenerator reportGenerator) {
return new JenkinsTrivyIntegration(trivyClient, analysisService, reportGenerator);
}
}
@Component
public class TrivyHealthIndicator implements HealthIndicator {
private final TrivyClient trivyClient;
public TrivyHealthIndicator(TrivyClient trivyClient) {
this.trivyClient = trivyClient;
}
@Override
public Health health() {
try {
trivyClient.validateTrivyInstallation();
return Health.up()
.withDetail("version", "trivy-version") // Extract actual version
.build();
} catch (Exception e) {
return Health.down()
.withDetail("error", e.getMessage())
.build();
}
}
}
2. REST API Controllers
@RestController
@RequestMapping("/api/security/trivy")
@Validated
public class TrivyScanController {
private final TrivyClient trivyClient;
private final VulnerabilityAnalysisService analysisService;
private final ReportGenerator reportGenerator;
public TrivyScanController(TrivyClient trivyClient,
VulnerabilityAnalysisService analysisService,
ReportGenerator reportGenerator) {
this.trivyClient = trivyClient;
this.analysisService = analysisService;
this.reportGenerator = reportGenerator;
}
@PostMapping("/scan/image")
public ResponseEntity<ScanResult> scanImage(@RequestBody ScanRequest request) {
try {
ScanResult result = trivyClient.scanImage(request.getImageName(), request.getConfig());
return ResponseEntity.ok(result);
} catch (TrivyException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(null); // Include error in response
}
}
@PostMapping("/scan/filesystem")
public ResponseEntity<ScanResult> scanFilesystem(@RequestBody FileScanRequest request) {
try {
ScanResult result = trivyClient.scanFilesystem(request.getPath(), request.getConfig());
return ResponseEntity.ok(result);
} catch (TrivyException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(null);
}
}
@PostMapping("/compliance/check")
public ResponseEntity<ComplianceReport> checkCompliance(@RequestBody ComplianceCheckRequest request) {
try {
ScanResult scanResult = trivyClient.scanImage(request.getImageName());
ComplianceReport report = analysisService.checkCompliance(scanResult, request.getPolicy());
return ResponseEntity.ok(report);
} catch (TrivyException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(null);
}
}
@GetMapping("/reports/{imageName}/html")
public ResponseEntity<String> getHtmlReport(@PathVariable String imageName) {
try {
ScanResult scanResult = trivyClient.scanImage(imageName);
ComplianceReport complianceReport = analysisService.checkCompliance(
scanResult, CompliancePolicy.defaultPolicy());
String htmlReport = reportGenerator.generateHtmlReport(scanResult, complianceReport);
return ResponseEntity.ok()
.contentType(MediaType.TEXT_HTML)
.body(htmlReport);
} catch (TrivyException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/dashboard")
public ResponseEntity<DashboardData> getDashboard() {
// Implementation to get recent scans and generate dashboard
List<ScanResult> recentScans = Collections.emptyList(); // Get from repository
DashboardData dashboard = reportGenerator.generateDashboardData(recentScans);
return ResponseEntity.ok(dashboard);
}
}
// Request DTOs
public class ScanRequest {
@NotBlank
private String imageName;
private ScanConfig config = ScanConfig.defaultConfig();
// Getters and setters
}
public class FileScanRequest {
@NotBlank
private String path;
private ScanConfig config = ScanConfig.defaultConfig();
// Getters and setters
}
public class ComplianceCheckRequest {
@NotBlank
private String imageName;
private CompliancePolicy policy = CompliancePolicy.defaultPolicy();
// Getters and setters
}
Configuration
1. Application Configuration
trivy:
trivy-path: "trivy"
cache-dir: "/tmp/trivy-cache"
remote-server: ${TRIVY_SERVER:}
severity-threshold: HIGH
timeout: 10m
skip-update: false
report:
format: "json"
output-dir: "./reports"
generate-html: true
generate-json: true
database:
update-interval: 24h
auto-update: true
management:
endpoints:
web:
exposure:
include: "health,trivy"
endpoint:
trivy:
enabled: true
logging:
level:
com.example.trivy: DEBUG
Exception Handling
public class TrivyException extends Exception {
public TrivyException(String message) {
super(message);
}
public TrivyException(String message, Throwable cause) {
super(message, cause);
}
}
@ControllerAdvice
public class TrivyExceptionHandler {
@ExceptionHandler(TrivyException.class)
public ResponseEntity<ErrorResponse> handleTrivyException(TrivyException e) {
ErrorResponse error = new ErrorResponse(
"TRIVY_SCAN_ERROR",
e.getMessage(),
Instant.now()
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
@ExceptionHandler(IllegalStateException.class)
public ResponseEntity<ErrorResponse> handleIllegalStateException(IllegalStateException e) {
ErrorResponse error = new ErrorResponse(
"TRIVY_CONFIG_ERROR",
e.getMessage(),
Instant.now()
);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
}
public class ErrorResponse {
private final String errorCode;
private final String message;
private final Instant timestamp;
public ErrorResponse(String errorCode, String message, Instant timestamp) {
this.errorCode = errorCode;
this.message = message;
this.timestamp = timestamp;
}
// Getters
}
Conclusion
Trivy Scanner integration in Java provides:
- Comprehensive Security Scanning: Container images, filesystems, and repositories
- CI/CD Pipeline Integration: Jenkins, GitHub Actions, and other CI systems
- Policy Enforcement: Compliance checking with configurable thresholds
- Advanced Analytics: Trend analysis, impact assessment, and risk scoring
- Comprehensive Reporting: HTML, JSON, CSV, and PDF reports with dashboards
Key benefits:
- Automated Security: Integrate security scanning into development workflows
- Policy as Code: Define and enforce security policies programmatically
- Visibility: Comprehensive reporting and dashboard capabilities
- Remediation Guidance: Fix recommendations and impact assessment
- Enterprise Ready: Scalable, configurable, and extensible architecture
This integration enables organizations to maintain robust security postures by systematically identifying and addressing vulnerabilities throughout the software development lifecycle.