Harbor Vulnerability Scan in Java: Comprehensive Container Security Integration

Harbor provides enterprise-grade container registry with built-in vulnerability scanning using Trivy, Clair, or other scanners. This guide covers integrating Harbor vulnerability scanning into Java applications and CI/CD pipelines.


Setup and Dependencies

1. Maven Dependencies
<!-- pom.xml -->
<properties>
<jib-maven-plugin.version>3.4.0</jib-maven-plugin.version>
<docker-java.version>3.3.4</docker-java.version>
<spring-boot.version>3.1.0</spring-boot.version>
</properties>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Docker Java API -->
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java</artifactId>
<version>${docker-java.version}</version>
</dependency>
<!-- HTTP Client for Harbor API -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>${spring-boot.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Jib for container building -->
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>${jib-maven-plugin.version}</version>
<configuration>
<from>
<image>eclipse-temurin:17-jre</image>
</from>
<to>
<image>harbor.example.com/my-project/${project.artifactId}:${project.version}</image>
<auth>
<username>${harbor.username}</username>
<password>${harbor.password}</password>
</auth>
</to>
<container>
<ports>
<port>8080</port>
</ports>
<environment>
<SPRING_PROFILES_ACTIVE>prod</SPRING_PROFILES_ACTIVE>
</environment>
</container>
</configuration>
</plugin>
</plugins>
</build>
2. Dockerfile for Multi-Stage Build
# Multi-stage Dockerfile
FROM eclipse-temurin:17-jdk as builder
WORKDIR /app
COPY . .
RUN ./mvnw clean package -DskipTests
FROM eclipse-temurin:17-jre as runtime
RUN addgroup --system --gid 1000 appuser && \
adduser --system --uid 1000 --gid 1000 appuser
USER appuser
WORKDIR /app
# Copy the application JAR
COPY --from=builder /app/target/*.jar app.jar
# Security hardening
RUN java -Djarmode=layertools -jar app.jar extract
# Create non-root user and set permissions
USER root
RUN chown -R appuser:appuser /app
USER appuser
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
3. Environment Setup
#!/bin/bash
# setup-harbor.sh
export HARBOR_URL=https://harbor.example.com
export HARBOR_USERNAME=admin
export HARBOR_PASSWORD=password
export HARBOR_PROJECT=my-project
export HARBOR_SCANNER=trivy
# Configure Docker to use Harbor
docker login $HARBOR_URL -u $HARBOR_USERNAME -p $HARBOR_PASSWORD
echo "Harbor environment configured"

Harbor API Integration

1. Harbor Client Service
package com.example.security.harbor;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.util.*;
@Service
public class HarborClientService {
private final String HARBOR_BASE_URL;
private final WebClient webClient;
private final RestTemplate restTemplate;
public HarborClientService(@Value("${harbor.url}") String harborUrl) {
this.HARBOR_BASE_URL = harborUrl;
this.webClient = WebClient.builder()
.baseUrl(harborUrl)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
this.restTemplate = new RestTemplate();
}
public HarborProject createProject(String projectName, String credentials) {
String url = HARBOR_BASE_URL + "/api/v2.0/projects";
HarborProject project = new HarborProject();
project.setProject_name(projectName);
project.setMetadata(new HashMap<>());
project.getMetadata().put("auto_scan", "true");
project.getMetadata().put("enable_content_trust", "true");
project.getMetadata().put("prevent_vul", "true");
project.getMetadata().put("severity", "high");
project.getMetadata().put("reuse_sys_cve_allowlist", "true");
HttpHeaders headers = createHeaders(credentials);
HttpEntity<HarborProject> entity = new HttpEntity<>(project, headers);
ResponseEntity<HarborProject> response = restTemplate.exchange(
url, HttpMethod.POST, entity, HarborProject.class);
return response.getBody();
}
public List<HarborRepository> getRepositories(String projectName, String credentials) {
String url = String.format("%s/api/v2.0/projects/%s/repositories", 
HARBOR_BASE_URL, projectName);
HttpHeaders headers = createHeaders(credentials);
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<HarborRepository[]> response = restTemplate.exchange(
url, HttpMethod.GET, entity, HarborRepository[].class);
return Arrays.asList(response.getBody());
}
public HarborArtifact getArtifact(String projectName, String repositoryName, 
String reference, String credentials) {
String url = String.format("%s/api/v2.0/projects/%s/repositories/%s/artifacts/%s", 
HARBOR_BASE_URL, projectName, repositoryName, reference);
HttpHeaders headers = createHeaders(credentials);
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<HarborArtifact> response = restTemplate.exchange(
url, HttpMethod.GET, entity, HarborArtifact.class);
return response.getBody();
}
public HarborScanReport getVulnerabilityScanReport(String projectName, String repositoryName,
String reference, String credentials) {
String url = String.format("%s/api/v2.0/projects/%s/repositories/%s/artifacts/%s/additions/vulnerabilities", 
HARBOR_BASE_URL, projectName, repositoryName, reference);
HttpHeaders headers = createHeaders(credentials);
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<HarborScanReport> response = restTemplate.exchange(
url, HttpMethod.GET, entity, HarborScanReport.class);
return response.getBody();
}
public Mono<Void> triggerImageScan(String projectName, String repositoryName,
String reference, String credentials) {
String url = String.format("%s/api/v2.0/projects/%s/repositories/%s/artifacts/%s/scan", 
HARBOR_BASE_URL, projectName, repositoryName, reference);
return webClient.post()
.uri(url)
.headers(headers -> headers.setBasicAuth(credentials))
.retrieve()
.bodyToMono(Void.class);
}
public HarborScanOverview getScanOverview(String projectName, String repositoryName,
String reference, String credentials) {
String url = String.format("%s/api/v2.0/projects/%s/repositories/%s/artifacts/%s?page=1&page_size=10&with_tag=true&with_label=false&with_scan_overview=true&with_signature=false&with_immutable_status=false", 
HARBOR_BASE_URL, projectName, repositoryName, reference);
HttpHeaders headers = createHeaders(credentials);
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<HarborArtifact> response = restTemplate.exchange(
url, HttpMethod.GET, entity, HarborArtifact.class);
return response.getBody().getScan_overview();
}
public List<HarborWebhook> getWebhooks(String projectName, String credentials) {
String url = String.format("%s/api/v2.0/projects/%s/webhook/policies", 
HARBOR_BASE_URL, projectName);
HttpHeaders headers = createHeaders(credentials);
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<HarborWebhook[]> response = restTemplate.exchange(
url, HttpMethod.GET, entity, HarborWebhook[].class);
return Arrays.asList(response.getBody());
}
public HarborWebhook createWebhook(String projectName, HarborWebhook webhook, String credentials) {
String url = String.format("%s/api/v2.0/projects/%s/webhook/policies", 
HARBOR_BASE_URL, projectName);
HttpHeaders headers = createHeaders(credentials);
HttpEntity<HarborWebhook> entity = new HttpEntity<>(webhook, headers);
ResponseEntity<HarborWebhook> response = restTemplate.exchange(
url, HttpMethod.POST, entity, HarborWebhook.class);
return response.getBody();
}
private HttpHeaders createHeaders(String credentials) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
if (credentials != null && !credentials.isEmpty()) {
String auth = "Basic " + Base64.getEncoder()
.encodeToString(credentials.getBytes());
headers.set("Authorization", auth);
}
return headers;
}
// DTO classes for Harbor API
public static class HarborProject {
private String project_name;
private Map<String, String> metadata;
public String getProject_name() { return project_name; }
public void setProject_name(String project_name) { this.project_name = project_name; }
public Map<String, String> getMetadata() { return metadata; }
public void setMetadata(Map<String, String> metadata) { this.metadata = metadata; }
}
public static class HarborRepository {
private String name;
private int artifact_count;
private String creation_time;
private String update_time;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getArtifact_count() { return artifact_count; }
public void setArtifact_count(int artifact_count) { this.artifact_count = artifact_count; }
public String getCreation_time() { return creation_time; }
public void setCreation_time(String creation_time) { this.creation_time = creation_time; }
public String getUpdate_time() { return update_time; }
public void setUpdate_time(String update_time) { this.update_time = update_time; }
}
public static class HarborArtifact {
private String digest;
private String size;
private String push_time;
private List<HarborTag> tags;
private HarborScanOverview scan_overview;
public String getDigest() { return digest; }
public void setDigest(String digest) { this.digest = digest; }
public String getSize() { return size; }
public void setSize(String size) { this.size = size; }
public String getPush_time() { return push_time; }
public void setPush_time(String push_time) { this.push_time = push_time; }
public List<HarborTag> getTags() { return tags; }
public void setTags(List<HarborTag> tags) { this.tags = tags; }
public HarborScanOverview getScan_overview() { return scan_overview; }
public void setScan_overview(HarborScanOverview scan_overview) { this.scan_overview = scan_overview; }
}
public static class HarborTag {
private String name;
private String push_time;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getPush_time() { return push_time; }
public void setPush_time(String push_time) { this.push_time = push_time; }
}
public static class HarborScanOverview {
private Map<String, HarborScanSummary> details;
public Map<String, HarborScanSummary> getDetails() { return details; }
public void setDetails(Map<String, HarborScanSummary> details) { this.details = details; }
}
public static class HarborScanSummary {
private int total;
private int fixable;
private String summary;
private String severity;
private String scan_status;
private String end_time;
public int getTotal() { return total; }
public void setTotal(int total) { this.total = total; }
public int getFixable() { return fixable; }
public void setFixable(int fixable) { this.fixable = fixable; }
public String getSummary() { return summary; }
public void setSummary(String summary) { this.summary = summary; }
public String getSeverity() { return severity; }
public void setSeverity(String severity) { this.severity = severity; }
public String getScan_status() { return scan_status; }
public void setScan_status(String scan_status) { this.scan_status = scan_status; }
public String getEnd_time() { return end_time; }
public void setEnd_time(String end_time) { this.end_time = end_time; }
}
public static class HarborScanReport {
private List<HarborVulnerability> vulnerabilities;
public List<HarborVulnerability> getVulnerabilities() { return vulnerabilities; }
public void setVulnerabilities(List<HarborVulnerability> vulnerabilities) { 
this.vulnerabilities = vulnerabilities; 
}
}
public static class HarborVulnerability {
private String id;
private String package;
private String version;
private String fix_version;
private String severity;
private String description;
private List<String> links;
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getPackage() { return package; }
public void setPackage(String package) { this.package = package; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
public String getFix_version() { return fix_version; }
public void setFix_version(String fix_version) { this.fix_version = fix_version; }
public String getSeverity() { return severity; }
public void setSeverity(String severity) { this.severity = severity; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public List<String> getLinks() { return links; }
public void setLinks(List<String> links) { this.links = links; }
}
public static class HarborWebhook {
private String name;
private String description;
private List<HarborWebhookTarget> targets;
private List<String> event_types;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return name; }
public void setDescription(String description) { this.description = description; }
public List<HarborWebhookTarget> getTargets() { return targets; }
public void setTargets(List<HarborWebhookTarget> targets) { this.targets = targets; }
public List<String> getEvent_types() { return event_types; }
public void setEvent_types(List<String> event_types) { this.event_types = event_types; }
}
public static class HarborWebhookTarget {
private String type;
private String address;
private String auth_header;
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
public String getAuth_header() { return auth_header; }
public void setAuth_header(String auth_header) { this.auth_header = auth_header; }
}
}
2. Vulnerability Analysis Service
package com.example.security.harbor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class VulnerabilityAnalysisService {
private static final Logger logger = LoggerFactory.getLogger(VulnerabilityAnalysisService.class);
private final HarborClientService harborClient;
private final SecurityAlertService alertService;
public VulnerabilityAnalysisService(HarborClientService harborClient, 
SecurityAlertService alertService) {
this.harborClient = harborClient;
this.alertService = alertService;
}
public VulnerabilityReport analyzeImage(String projectName, String repositoryName, 
String tag, String credentials) {
VulnerabilityReport report = new VulnerabilityReport();
report.setImageName(repositoryName + ":" + tag);
report.setAnalysisTime(new Date());
try {
// Get scan overview
HarborClientService.HarborScanOverview scanOverview = 
harborClient.getScanOverview(projectName, repositoryName, tag, credentials);
if (scanOverview != null && scanOverview.getDetails() != null) {
for (Map.Entry<String, HarborClientService.HarborScanSummary> entry : 
scanOverview.getDetails().entrySet()) {
HarborClientService.HarborScanSummary summary = entry.getValue();
report.setTotalVulnerabilities(summary.getTotal());
report.setFixableVulnerabilities(summary.getFixable());
report.setHighestSeverity(summary.getSeverity());
report.setScanStatus(summary.getScan_status());
// Get detailed vulnerability report
HarborClientService.HarborScanReport scanReport = 
harborClient.getVulnerabilityScanReport(projectName, repositoryName, tag, credentials);
if (scanReport != null && scanReport.getVulnerabilities() != null) {
processVulnerabilities(scanReport.getVulnerabilities(), report);
}
}
}
report.setSuccessful(true);
} catch (Exception e) {
logger.error("Failed to analyze image vulnerabilities: {}", repositoryName + ":" + tag, e);
report.setSuccessful(false);
report.setErrorMessage(e.getMessage());
}
return report;
}
@Scheduled(cron = "0 0 6 * * ?") // Daily at 6 AM
public void performDailyVulnerabilityScan() {
logger.info("Starting daily vulnerability scan for all images");
String credentials = getHarborCredentials();
String projectName = System.getenv("HARBOR_PROJECT") ?? "default";
try {
List<HarborClientService.HarborRepository> repositories = 
harborClient.getRepositories(projectName, credentials);
for (HarborClientService.HarborRepository repo : repositories) {
scanRepositoryImages(projectName, repo.getName(), credentials);
}
} catch (Exception e) {
logger.error("Daily vulnerability scan failed", e);
alertService.sendAlert("Daily vulnerability scan failed", e.getMessage());
}
}
public ComplianceCheckResult checkCompliance(String projectName, String repositoryName,
String tag, CompliancePolicy policy, String credentials) {
ComplianceCheckResult result = new ComplianceCheckResult();
result.setImageName(repositoryName + ":" + tag);
result.setPolicyName(policy.getName());
VulnerabilityReport vulnReport = analyzeImage(projectName, repositoryName, tag, credentials);
if (!vulnReport.isSuccessful()) {
result.setCompliant(false);
result.setReason("Failed to analyze vulnerabilities");
return result;
}
// Check against policy
if (vulnReport.getTotalVulnerabilities() > policy.getMaxTotalVulnerabilities()) {
result.setCompliant(false);
result.setReason("Exceeds maximum total vulnerabilities");
result.setViolations(vulnReport.getCriticalVulnerabilities() + 
vulnReport.getHighVulnerabilities());
return result;
}
if (vulnReport.getCriticalVulnerabilities() > policy.getMaxCriticalVulnerabilities()) {
result.setCompliant(false);
result.setReason("Exceeds maximum critical vulnerabilities");
result.setViolations(vulnReport.getCriticalVulnerabilities());
return result;
}
if (vulnReport.getHighVulnerabilities() > policy.getMaxHighVulnerabilities()) {
result.setCompliant(false);
result.setReason("Exceeds maximum high vulnerabilities");
result.setViolations(vulnReport.getHighVulnerabilities());
return result;
}
result.setCompliant(true);
result.setReason("Compliant with security policy");
return result;
}
private void scanRepositoryImages(String projectName, String repositoryName, String credentials) {
try {
// Get artifact details to find tags
HarborClientService.HarborArtifact artifact = 
harborClient.getArtifact(projectName, repositoryName, "latest", credentials);
if (artifact != null && artifact.getTags() != null) {
for (HarborClientService.HarborTag tag : artifact.getTags()) {
VulnerabilityReport report = analyzeImage(projectName, repositoryName, 
tag.getName(), credentials);
if (!report.isSuccessful()) {
logger.warn("Failed to scan {}:{}", repositoryName, tag.getName());
continue;
}
// Check if we need to alert
if (report.getCriticalVulnerabilities() > 0 || 
report.getHighVulnerabilities() > 5) {
alertService.sendAlert(
"High vulnerability count in " + repositoryName + ":" + tag.getName(),
String.format("Critical: %d, High: %d, Total: %d", 
report.getCriticalVulnerabilities(),
report.getHighVulnerabilities(),
report.getTotalVulnerabilities())
);
}
}
}
} catch (Exception e) {
logger.error("Failed to scan repository: {}", repositoryName, e);
}
}
private void processVulnerabilities(List<HarborClientService.HarborVulnerability> vulnerabilities, 
VulnerabilityReport report) {
int critical = 0;
int high = 0;
int medium = 0;
int low = 0;
int unknown = 0;
List<Vulnerability> criticalVulns = new ArrayList<>();
List<Vulnerability> highVulns = new ArrayList<>();
for (HarborClientService.HarborVulnerability vuln : vulnerabilities) {
switch (vuln.getSeverity().toLowerCase()) {
case "critical":
critical++;
criticalVulns.add(convertToVulnerability(vuln));
break;
case "high":
high++;
highVulns.add(convertToVulnerability(vuln));
break;
case "medium":
medium++;
break;
case "low":
low++;
break;
default:
unknown++;
break;
}
}
report.setCriticalVulnerabilities(critical);
report.setHighVulnerabilities(high);
report.setMediumVulnerabilities(medium);
report.setLowVulnerabilities(low);
report.setUnknownVulnerabilities(unknown);
report.setCriticalVulnerabilityList(criticalVulns);
report.setHighVulnerabilityList(highVulns);
}
private Vulnerability convertToVulnerability(HarborClientService.HarborVulnerability harborVuln) {
Vulnerability vuln = new Vulnerability();
vuln.setId(harborVuln.getId());
vuln.setPackageName(harborVuln.getPackage());
vuln.setVersion(harborVuln.getVersion());
vuln.setFixedVersion(harborVuln.getFix_version());
vuln.setSeverity(harborVuln.getSeverity());
vuln.setDescription(harborVuln.getDescription());
return vuln;
}
private String getHarborCredentials() {
String username = System.getenv("HARBOR_USERNAME");
String password = System.getenv("HARBOR_PASSWORD");
return username + ":" + password;
}
// DTO classes
public static class VulnerabilityReport {
private String imageName;
private Date analysisTime;
private boolean successful;
private String errorMessage;
private int totalVulnerabilities;
private int fixableVulnerabilities;
private int criticalVulnerabilities;
private int highVulnerabilities;
private int mediumVulnerabilities;
private int lowVulnerabilities;
private int unknownVulnerabilities;
private String highestSeverity;
private String scanStatus;
private List<Vulnerability> criticalVulnerabilityList;
private List<Vulnerability> highVulnerabilityList;
// Getters and setters
public String getImageName() { return imageName; }
public void setImageName(String imageName) { this.imageName = imageName; }
public Date getAnalysisTime() { return analysisTime; }
public void setAnalysisTime(Date analysisTime) { this.analysisTime = analysisTime; }
public boolean isSuccessful() { return successful; }
public void setSuccessful(boolean successful) { this.successful = successful; }
public String getErrorMessage() { return errorMessage; }
public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; }
public int getTotalVulnerabilities() { return totalVulnerabilities; }
public void setTotalVulnerabilities(int totalVulnerabilities) { this.totalVulnerabilities = totalVulnerabilities; }
public int getFixableVulnerabilities() { return fixableVulnerabilities; }
public void setFixableVulnerabilities(int fixableVulnerabilities) { this.fixableVulnerabilities = fixableVulnerabilities; }
public int getCriticalVulnerabilities() { return criticalVulnerabilities; }
public void setCriticalVulnerabilities(int criticalVulnerabilities) { this.criticalVulnerabilities = criticalVulnerabilities; }
public int getHighVulnerabilities() { return highVulnerabilities; }
public void setHighVulnerabilities(int highVulnerabilities) { this.highVulnerabilities = highVulnerabilities; }
public int getMediumVulnerabilities() { return mediumVulnerabilities; }
public void setMediumVulnerabilities(int mediumVulnerabilities) { this.mediumVulnerabilities = mediumVulnerabilities; }
public int getLowVulnerabilities() { return lowVulnerabilities; }
public void setLowVulnerabilities(int lowVulnerabilities) { this.lowVulnerabilities = lowVulnerabilities; }
public int getUnknownVulnerabilities() { return unknownVulnerabilities; }
public void setUnknownVulnerabilities(int unknownVulnerabilities) { this.unknownVulnerabilities = unknownVulnerabilities; }
public String getHighestSeverity() { return highestSeverity; }
public void setHighestSeverity(String highestSeverity) { this.highestSeverity = highestSeverity; }
public String getScanStatus() { return scanStatus; }
public void setScanStatus(String scanStatus) { this.scanStatus = scanStatus; }
public List<Vulnerability> getCriticalVulnerabilityList() { return criticalVulnerabilityList; }
public void setCriticalVulnerabilityList(List<Vulnerability> criticalVulnerabilityList) { this.criticalVulnerabilityList = criticalVulnerabilityList; }
public List<Vulnerability> getHighVulnerabilityList() { return highVulnerabilityList; }
public void setHighVulnerabilityList(List<Vulnerability> highVulnerabilityList) { this.highVulnerabilityList = highVulnerabilityList; }
}
public static class Vulnerability {
private String id;
private String packageName;
private String version;
private String fixedVersion;
private String severity;
private String description;
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getPackageName() { return packageName; }
public void setPackageName(String packageName) { this.packageName = packageName; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
public String getFixedVersion() { return fixedVersion; }
public void setFixedVersion(String fixedVersion) { this.fixedVersion = fixedVersion; }
public String getSeverity() { return severity; }
public void setSeverity(String severity) { this.severity = severity; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
}
public static class CompliancePolicy {
private String name;
private int maxTotalVulnerabilities;
private int maxCriticalVulnerabilities;
private int maxHighVulnerabilities;
private boolean allowUnknownSeverity;
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getMaxTotalVulnerabilities() { return maxTotalVulnerabilities; }
public void setMaxTotalVulnerabilities(int maxTotalVulnerabilities) { this.maxTotalVulnerabilities = maxTotalVulnerabilities; }
public int getMaxCriticalVulnerabilities() { return maxCriticalVulnerabilities; }
public void setMaxCriticalVulnerabilities(int maxCriticalVulnerabilities) { this.maxCriticalVulnerabilities = maxCriticalVulnerabilities; }
public int getMaxHighVulnerabilities() { return maxHighVulnerabilities; }
public void setMaxHighVulnerabilities(int maxHighVulnerabilities) { this.maxHighVulnerabilities = maxHighVulnerabilities; }
public boolean isAllowUnknownSeverity() { return allowUnknownSeverity; }
public void setAllowUnknownSeverity(boolean allowUnknownSeverity) { this.allowUnknownSeverity = allowUnknownSeverity; }
}
public static class ComplianceCheckResult {
private String imageName;
private String policyName;
private boolean compliant;
private String reason;
private int violations;
// Getters and setters
public String getImageName() { return imageName; }
public void setImageName(String imageName) { this.imageName = imageName; }
public String getPolicyName() { return policyName; }
public void setPolicyName(String policyName) { this.policyName = policyName; }
public boolean isCompliant() { return compliant; }
public void setCompliant(boolean compliant) { this.compliant = compliant; }
public String getReason() { return reason; }
public void setReason(String reason) { this.reason = reason; }
public int getViolations() { return violations; }
public void setViolations(int violations) { this.violations = violations; }
}
}
3. Security Alert Service
package com.example.security.harbor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
@Service
public class SecurityAlertService {
private static final Logger logger = LoggerFactory.getLogger(SecurityAlertService.class);
private final JavaMailSender mailSender;
private final RestTemplate restTemplate;
@Value("${security.alerts.email.recipient:[email protected]}")
private String alertEmailRecipient;
@Value("${security.alerts.slack.webhook:}")
private String slackWebhookUrl;
@Value("${security.alerts.teams.webhook:}")
private String teamsWebhookUrl;
public SecurityAlertService(JavaMailSender mailSender, RestTemplate restTemplate) {
this.mailSender = mailSender;
this.restTemplate = restTemplate;
}
public void sendAlert(String subject, String message) {
sendEmailAlert(subject, message);
sendSlackAlert(subject, message);
sendTeamsAlert(subject, message);
logSecurityAlert(subject, message);
}
public void sendCriticalAlert(String subject, String message) {
String criticalSubject = "🚨 CRITICAL: " + subject;
sendEmailAlert(criticalSubject, message);
sendSlackAlert(criticalSubject, message);
sendTeamsAlert(criticalSubject, message);
logSecurityAlert(criticalSubject, message);
}
private void sendEmailAlert(String subject, String message) {
try {
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setTo(alertEmailRecipient);
mailMessage.setSubject("[Harbor Security] " + subject);
mailMessage.setText(message);
mailSender.send(mailMessage);
logger.info("Security alert email sent: {}", subject);
} catch (Exception e) {
logger.error("Failed to send security alert email", e);
}
}
private void sendSlackAlert(String subject, String message) {
if (slackWebhookUrl == null || slackWebhookUrl.isEmpty()) {
return;
}
try {
Map<String, Object> slackMessage = new HashMap<>();
slackMessage.put("text", "🔒 *Harbor Security Alert*\n*" + subject + "*\n" + message);
restTemplate.postForEntity(slackWebhookUrl, slackMessage, String.class);
logger.info("Security alert sent to Slack: {}", subject);
} catch (Exception e) {
logger.error("Failed to send security alert to Slack", e);
}
}
private void sendTeamsAlert(String subject, String message) {
if (teamsWebhookUrl == null || teamsWebhookUrl.isEmpty()) {
return;
}
try {
Map<String, Object> teamsMessage = new HashMap<>();
teamsMessage.put("@type", "MessageCard");
teamsMessage.put("@context", "http://schema.org/extensions");
teamsMessage.put("themeColor", "FF0000");
teamsMessage.put("summary", subject);
teamsMessage.put("sections", new Object[]{
Map.of(
"activityTitle", "Harbor Security Alert",
"activitySubtitle", subject,
"text", message,
"markdown", true
)
});
restTemplate.postForEntity(teamsWebhookUrl, teamsMessage, String.class);
logger.info("Security alert sent to Teams: {}", subject);
} catch (Exception e) {
logger.error("Failed to send security alert to Teams", e);
}
}
private void logSecurityAlert(String subject, String message) {
logger.warn("SECURITY ALERT - {}: {}", subject, message);
}
}

CI/CD Integration

1. GitHub Actions Workflow
# .github/workflows/harbor-scan.yml
name: Harbor Container Security Scan
on:
push:
branches: [ main, develop ]
tags: [ 'v*' ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 6 * * 1'  # Weekly Monday at 6 AM
jobs:
build-and-scan:
name: Build and Security Scan
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build with Maven
run: mvn clean package -DskipTests
- name: Build Docker image
run: |
docker build -t harbor.example.com/my-project/app:${{ github.sha }} .
docker tag harbor.example.com/my-project/app:${{ github.sha }} harbor.example.com/my-project/app:latest
- name: Log in to Harbor
run: |
docker login harbor.example.com -u ${{ secrets.HARBOR_USERNAME }} -p ${{ secrets.HARBOR_PASSWORD }}
- name: Push to Harbor
run: |
docker push harbor.example.com/my-project/app:${{ github.sha }}
docker push harbor.example.com/my-project/app:latest
- name: Trigger Harbor Scan
run: |
curl -X POST \
-u ${{ secrets.HARBOR_USERNAME }}:${{ secrets.HARBOR_PASSWORD }} \
"https://harbor.example.com/api/v2.0/projects/my-project/repositories/app/artifacts/${{ github.sha }}/scan"
- name: Wait for Scan Completion
run: |
sleep 30  # Wait for scan to complete
curl -X GET \
-u ${{ secrets.HARBOR_USERNAME }}:${{ secrets.HARBOR_PASSWORD }} \
"https://harbor.example.com/api/v2.0/projects/my-project/repositories/app/artifacts/${{ github.sha }}?with_scan_overview=true" > scan-result.json
- name: Check Scan Results
run: |
TOTAL_VULNS=$(jq '.scan_overview.details."application/vnd.security.vulnerability.report; version=1.1".summary.total' scan-result.json)
CRITICAL_VULNS=$(jq '.scan_overview.details."application/vnd.security.vulnerability.report; version=1.1".summary.severity' scan-result.json)
if [ "$TOTAL_VULNS" -gt "10" ] || [ "$CRITICAL_VULNS" = "Critical" ]; then
echo "❌ Security vulnerabilities detected"
exit 1
else
echo "✅ Security scan passed"
fi
- name: Upload Scan Report
uses: actions/upload-artifact@v3
with:
name: security-scan-report
path: scan-result.json
2. Jenkins Pipeline
// Jenkinsfile
pipeline {
agent any
environment {
HARBOR_URL = 'https://harbor.example.com'
HARBOR_PROJECT = 'my-project'
HARBOR_CREDENTIALS = credentials('harbor-credentials')
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh 'mvn clean package -DskipTests'
}
}
stage('Build Docker Image') {
steps {
script {
docker.build("${HARBOR_URL}/${HARBOR_PROJECT}/app:${env.BUILD_ID}")
}
}
}
stage('Push to Harbor') {
steps {
script {
docker.withRegistry(HARBOR_URL, 'harbor-credentials') {
docker.image("${HARBOR_URL}/${HARBOR_PROJECT}/app:${env.BUILD_ID}").push()
docker.image("${HARBOR_URL}/${HARBOR_PROJECT}/app:${env.BUILD_ID}").push('latest')
}
}
}
}
stage('Security Scan') {
steps {
script {
// Trigger Harbor scan
sh """
curl -X POST \
-u ${HARBOR_CREDENTIALS} \
"${HARBOR_URL}/api/v2.0/projects/${HARBOR_PROJECT}/repositories/app/artifacts/${env.BUILD_ID}/scan"
"""
// Wait for scan completion
sleep time: 30, unit: 'SECONDS'
// Get scan results
sh """
curl -X GET \
-u ${HARBOR_CREDENTIALS} \
"${HARBOR_URL}/api/v2.0/projects/${HARBOR_PROJECT}/repositories/app/artifacts/${env.BUILD_ID}?with_scan_overview=true" > scan-result.json
"""
// Parse and evaluate results
def scanResult = readJSON file: 'scan-result.json'
def scanSummary = scanResult.scan_overview.details.'application/vnd.security.vulnerability.report; version=1.1'.summary
if (scanSummary.total > 10 || scanSummary.severity == 'Critical') {
error "Security vulnerabilities detected: ${scanSummary.total} total, ${scanSummary.severity} severity"
}
}
}
}
stage('Deploy') {
when {
expression { 
currentBuild.result == null || currentBuild.result == 'SUCCESS' 
}
}
steps {
echo 'Deploying to production...'
// Add deployment steps here
}
}
}
post {
always {
archiveArtifacts artifacts: 'scan-result.json', fingerprint: true
}
failure {
emailext (
subject: "Security Scan Failed: ${env.JOB_NAME}",
body: "Container security vulnerabilities detected in build ${env.BUILD_URL}",
to: "[email protected]"
)
}
}
}
3. Webhook Controller for Harbor Events
package com.example.security.harbor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/webhooks/harbor")
public class HarborWebhookController {
private static final Logger logger = LoggerFactory.getLogger(HarborWebhookController.class);
private final VulnerabilityAnalysisService vulnerabilityService;
private final SecurityAlertService alertService;
public HarborWebhookController(VulnerabilityAnalysisService vulnerabilityService,
SecurityAlertService alertService) {
this.vulnerabilityService = vulnerabilityService;
this.alertService = alertService;
}
@PostMapping("/scan-completed")
public void handleScanCompleted(@RequestBody HarborWebhookPayload payload) {
logger.info("Received scan completed webhook for {}", payload.getEvent_data().getResources()[0].getResource_url());
try {
// Extract image information from payload
String resourceUrl = payload.getEvent_data().getResources()[0].getResource_url();
String[] parts = resourceUrl.split("/");
String projectName = parts[parts.length - 3];
String repositoryName = parts[parts.length - 2];
String tag = parts[parts.length - 1];
// Analyze the scan results
String credentials = getHarborCredentials();
VulnerabilityAnalysisService.VulnerabilityReport report = 
vulnerabilityService.analyzeImage(projectName, repositoryName, tag, credentials);
if (!report.isSuccessful()) {
logger.error("Failed to analyze scan results for {}", resourceUrl);
return;
}
// Check compliance
VulnerabilityAnalysisService.CompliancePolicy policy = createDefaultPolicy();
VulnerabilityAnalysisService.ComplianceCheckResult compliance = 
vulnerabilityService.checkCompliance(projectName, repositoryName, tag, policy, credentials);
if (!compliance.isCompliant()) {
alertService.sendAlert(
"Non-compliant image detected: " + resourceUrl,
"Reason: " + compliance.getReason() + ", Violations: " + compliance.getViolations()
);
}
logger.info("Scan analysis completed for {}: {}", resourceUrl, 
compliance.isCompliant() ? "COMPLIANT" : "NON-COMPLIANT");
} catch (Exception e) {
logger.error("Failed to process scan completed webhook", e);
}
}
@PostMapping("/image-pushed")
public void handleImagePushed(@RequestBody HarborWebhookPayload payload) {
logger.info("Received image pushed webhook for {}", payload.getEvent_data().getResources()[0].getResource_url());
// Trigger automatic scan for newly pushed images
String resourceUrl = payload.getEvent_data().getResources()[0].getResource_url();
logger.info("New image pushed: {}", resourceUrl);
}
@PostMapping("/image-deleted")
public void handleImageDeleted(@RequestBody HarborWebhookPayload payload) {
logger.info("Received image deleted webhook for {}", payload.getEvent_data().getResources()[0].getResource_url());
// Clean up any related records
String resourceUrl = payload.getEvent_data().getResources()[0].getResource_url();
logger.info("Image deleted: {}", resourceUrl);
}
private String getHarborCredentials() {
String username = System.getenv("HARBOR_USERNAME");
String password = System.getenv("HARBOR_PASSWORD");
return username + ":" + password;
}
private VulnerabilityAnalysisService.CompliancePolicy createDefaultPolicy() {
VulnerabilityAnalysisService.CompliancePolicy policy = 
new VulnerabilityAnalysisService.CompliancePolicy();
policy.setName("default-policy");
policy.setMaxTotalVulnerabilities(50);
policy.setMaxCriticalVulnerabilities(0);
policy.setMaxHighVulnerabilities(5);
policy.setAllowUnknownSeverity(false);
return policy;
}
// Webhook payload DTO
public static class HarborWebhookPayload {
private String type;
private String occur_at;
private String operator;
private EventData event_data;
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public String getOccur_at() { return occur_at; }
public void setOccur_at(String occur_at) { this.occur_at = occur_at; }
public String getOperator() { return operator; }
public void setOperator(String operator) { this.operator = operator; }
public EventData getEvent_data() { return event_data; }
public void setEvent_data(EventData event_data) { this.event_data = event_data; }
}
public static class EventData {
private Resource[] resources;
private String repository;
public Resource[] getResources() { return resources; }
public void setResources(Resource[] resources) { this.resources = resources; }
public String getRepository() { return repository; }
public void setRepository(String repository) { this.repository = repository; }
}
public static class Resource {
private String digest;
private String tag;
private String resource_url;
public String getDigest() { return digest; }
public void setDigest(String digest) { this.digest = digest; }
public String getTag() { return tag; }
public void setTag(String tag) { this.tag = tag; }
public String getResource_url() { return resource_url; }
public void setResource_url(String resource_url) { this.resource_url = resource_url; }
}
}

Best Practices

  1. Image Security:
  • Use minimal base images
  • Regular base image updates
  • Multi-stage builds to reduce attack surface
  • Non-root user execution
  1. Scanning Strategy:
  • Scan on every push
  • Regular scheduled scans
  • Block deployment on critical vulnerabilities
  • Automated fix suggestions
  1. Compliance:
  • Define clear security policies
  • Regular compliance audits
  • Automated policy enforcement
  • Executive reporting
  1. Integration:
  • Webhook-based automation
  • CI/CD pipeline integration
  • Real-time alerting
  • Centralized dashboard
# Useful Harbor CLI commands
harbor-cli project create my-project --public=false
harbor-cli repository list --project my-project
harbor-cli artifact scan my-project/app:latest
harbor-cli vulnerability list my-project/app:latest

Conclusion

Harbor Vulnerability Scanning for Java provides:

  • Comprehensive container security scanning
  • Integration with CI/CD pipelines
  • Policy-based compliance checking
  • Real-time monitoring and alerting
  • Centralized management of container images

By implementing the patterns and configurations shown above, you can establish a robust container security practice, ensure compliance with security policies, identify and remediate vulnerabilities early, and maintain a secure software supply chain for your Java applications.

Secure Java Dependency Management, Vulnerability Scanning & Software Supply Chain Protection (SBOM, SCA, CI Security & License Compliance)

https://macronepal.com/blog/github-code-scanning-in-java-complete-guide/
Explains GitHub Code Scanning for Java using tools like CodeQL to automatically analyze source code and detect security vulnerabilities directly inside CI/CD pipelines before deployment.

https://macronepal.com/blog/license-compliance-in-java-comprehensive-guide/
Explains software license compliance in Java projects, ensuring dependencies follow legal requirements (MIT, Apache, GPL, etc.) and preventing license violations in enterprise software.

https://macronepal.com/blog/container-security-for-java-uncovering-vulnerabilities-with-grype/
Explains using Grype to scan Java container images and filesystems for known CVEs in OS packages and application dependencies to improve container security.

https://macronepal.com/blog/syft-sbom-generation-in-java-comprehensive-software-bill-of-materials-for-jvm-applications/
Explains using Syft to generate SBOMs (Software Bill of Materials) for Java applications, listing all dependencies, libraries, and components for supply chain transparency.

https://macronepal.com/blog/comprehensive-dependency-analysis-generating-and-scanning-sboms-with-trivy-for-java/
Explains using Trivy to generate SBOMs and scan Java dependencies and container images for vulnerabilities, integrating security checks into CI/CD pipelines.

https://macronepal.com/blog/dependabot-for-java-in-java/
Explains GitHub Dependabot for Java projects, which automatically detects vulnerable dependencies and creates pull requests to update them securely.

https://macronepal.com/blog/parasoft-jtest-in-java-comprehensive-guide-to-code-analysis-and-testing/
Explains Parasoft Jtest, a static analysis and testing tool for Java that helps detect bugs, security issues, and code quality problems early in development.

https://macronepal.com/blog/snyk-open-source-in-java-comprehensive-dependency-vulnerability-management-2/
Explains Snyk Open Source for Java, which continuously scans dependencies for vulnerabilities and provides automated fix suggestions and monitoring.

https://macronepal.com/blog/owasp-dependency-check-in-java-complete-vulnerability-scanning-guide/
Explains OWASP Dependency-Check, which scans Java dependencies against the National Vulnerability Database (NVD) to detect known security vulnerabilities.

https://macronepal.com/blog/securing-your-dependencies-a-java-developers-guide-to-whitesource-mend-bolt/
Explains Mend (WhiteSource) Bolt for Java, a dependency management and SCA tool that provides vulnerability detection, license compliance, and security policy enforcement in enterprise environments.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper