Black Duck Hub in Java: Complete Software Composition Analysis Guide


Article

Black Duck Hub is a comprehensive Software Composition Analysis (SCA) tool that helps organizations identify and manage open-source security vulnerabilities, license compliance risks, and operational risks in their software projects. This guide covers everything from basic setup to advanced integration patterns for Java applications.

Why Black Duck Hub for Java?

  • Vulnerability Detection: Identifies known security vulnerabilities in dependencies
  • License Compliance: Manages open-source license obligations and restrictions
  • Bill of Materials: Creates comprehensive software bill of materials (SBOM)
  • Policy Enforcement: Enforces organizational security and compliance policies
  • CI/CD Integration: Seamless integration with build pipelines
  • Remediation Guidance: Provides actionable fix recommendations

Project Setup and Dependencies

Maven Integration:

<properties>
<blackduck.version>2023.4.0</blackduck.version>
<detect.version>8.9.0</detect.version>
</properties>
<build>
<plugins>
<!-- Black Duck Detect Maven Plugin -->
<plugin>
<groupId>com.synopsys.integration</groupId>
<artifactId>blackduck-maven-plugin</artifactId>
<version>${blackduck.version}</version>
<configuration>
<blackduck.url>https://your-blackduck-server.com</blackduck.url>
<blackduck.api.token>${blackduck.api.token}</blackduck.api.token>
<project.name>${project.artifactId}</project.name>
<project.version>${project.version}</project.version>
<output.path>${project.build.directory}/blackduck</output.path>
<scan.target.path>${project.basedir}</scan.target.path>
<tools>DETECT</tools>
</configuration>
<executions>
<execution>
<id>blackduck-scan</id>
<phase>verify</phase>
<goals>
<goal>scan</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Gradle Configuration:

plugins {
id 'com.synopsys.integration.blackduck' version '2023.4.0'
}
blackduck {
url = 'https://your-blackduck-server.com'
apiToken = System.getenv('BLACKDUCK_API_TOKEN')
projectName = project.name
projectVersion = project.version
outputDirectory = file("${buildDir}/blackduck")
scanTargetPath = project.projectDir
tools = ['DETECT']
}

1. Black Duck Detect Configuration

detect.properties:

# Black Duck Server Configuration
blackduck.url=https://your-blackduck-server.com
blackduck.api.token=${BLACKDUCK_API_TOKEN}
blackduck.trust.cert=true
# Project Configuration
detect.project.name=order-service
detect.project.version=1.0.0
detect.project.tier=1
# Scan Configuration
detect.source.path=.
detect.output.path=./blackduck-output
detect.tools=DETECTOR,BOM_TOOL
# Detector Configuration
detect.detector.search.depth=10
detect.detector.search.continue=true
detect.detector.excluded.directories=build,target,.gradle,.idea,node_modules
# Maven Specific
detect.maven.build.command=clean compile
detect.maven.path=mvn
detect.maven.included.scopes=compile,runtime
# Gradle Specific  
detect.gradle.build.command=clean build
detect.gradle.path=gradle
detect.gradle.included.configurations=implementation,runtimeClasspath
# Parallel Processing
detect.parallel.processors=4
detect.parallel.processor.timeout=120
# Policy Check
detect.policy.check.fail.on.severities=BLOCKER,CRITICAL,HIGH
detect.policy.check.fail.on.names=SECURITY_VULNERABILITY,FORBIDDEN_LICENSE
# Notifications
detect.notification.types=EMAIL,SLACK
[email protected]
detect.notification.SLACK.webhook.url=${SLACK_WEBHOOK_URL}
# Advanced Settings
detect.binary.scan.file.path=.
detect.docker.image=
detect.docker.platform.linux.images=alpine:latest
detect.cleanup=false

2. Java Application Integration

Black Duck Service Class:

package com.example.security.blackduck;
import com.synopsys.integration.blackduck.api.generated.view.*;
import com.synopsys.integration.blackduck.configuration.BlackDuckServerConfig;
import com.synopsys.integration.blackduck.service.BlackDuckApiClient;
import com.synopsys.integration.blackduck.service.BlackDuckServicesFactory;
import com.synopsys.integration.blackduck.service.dataservice.*;
import com.synopsys.integration.exception.IntegrationException;
import com.synopsys.integration.log.IntLogger;
import com.synopsys.integration.log.Slf4jIntLogger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class BlackDuckService {
private static final Logger logger = LoggerFactory.getLogger(BlackDuckService.class);
private final BlackDuckServicesFactory blackDuckServicesFactory;
private final ProjectService projectService;
private final ComponentService componentService;
private final PolicyService policyService;
private final ReportService reportService;
public BlackDuckService(BlackDuckConfig blackDuckConfig) {
try {
IntLogger intLogger = new Slf4jIntLogger(logger);
BlackDuckServerConfig serverConfig = BlackDuckServerConfig.newBuilder()
.setUrl(blackDuckConfig.getUrl())
.setApiToken(blackDuckConfig.getApiToken())
.setTrustCert(blackDuckConfig.isTrustCert())
.setTimeout(blackDuckConfig.getTimeout())
.build();
this.blackDuckServicesFactory = serverConfig.createBlackDuckServicesFactory(intLogger);
this.projectService = blackDuckServicesFactory.createProjectService();
this.componentService = blackDuckServicesFactory.createComponentService();
this.policyService = blackDuckServicesFactory.createPolicyService();
this.reportService = blackDuckServicesFactory.createReportService();
} catch (IntegrationException e) {
throw new RuntimeException("Failed to initialize Black Duck client", e);
}
}
public ProjectView getOrCreateProject(String projectName, String version) throws IntegrationException {
Optional<ProjectView> existingProject = projectService.getProjectByName(projectName);
if (existingProject.isPresent()) {
logger.info("Found existing project: {}", projectName);
return existingProject.get();
} else {
logger.info("Creating new project: {}", projectName);
ProjectRequest projectRequest = new ProjectRequest();
projectRequest.setName(projectName);
projectRequest.setProjectLevelAdjustments(true);
return projectService.createProject(projectRequest);
}
}
public List<ComponentVulnerabilityView> getProjectVulnerabilities(String projectName, String version) 
throws IntegrationException {
Optional<ProjectView> project = projectService.getProjectByName(projectName);
if (!project.isPresent()) {
throw new IllegalArgumentException("Project not found: " + projectName);
}
Optional<ProjectVersionView> projectVersion = projectService.getProjectVersion(project.get(), version);
if (!projectVersion.isPresent()) {
throw new IllegalArgumentException("Project version not found: " + version);
}
return componentService.getComponentVulnerabilities(projectVersion.get());
}
public Map<RiskPriority, List<ComponentView>> getComponentsByRisk(String projectName, String version) 
throws IntegrationException {
Optional<ProjectView> project = projectService.getProjectByName(projectName);
Optional<ProjectVersionView> projectVersion = projectService.getProjectVersion(project.get(), version);
List<ComponentView> components = componentService.getAllComponents(projectVersion.get());
return components.stream()
.collect(Collectors.groupingBy(component -> {
// Calculate risk based on vulnerabilities and license
int riskScore = calculateRiskScore(component);
return getRiskPriority(riskScore);
}));
}
public PolicyStatusView getPolicyStatus(String projectName, String version) throws IntegrationException {
Optional<ProjectView> project = projectService.getProjectByName(projectName);
Optional<ProjectVersionView> projectVersion = projectService.getProjectVersion(project.get(), version);
return policyService.getPolicyStatusForProjectVersion(projectVersion.get());
}
public void generateRiskReport(String projectName, String version, String outputPath) 
throws IntegrationException {
Optional<ProjectView> project = projectService.getProjectByName(projectName);
Optional<ProjectVersionView> projectVersion = projectService.getProjectVersion(project.get(), version);
ReportRequest reportRequest = new ReportRequest();
reportRequest.setReportFormat(ReportFormat.JSON);
reportRequest.setReportType(ReportType.VERSION);
reportRequest.setProjectVersion(projectVersion.get());
reportService.generateReport(reportRequest, outputPath);
}
public List<ComponentView> getComponentsWithVulnerabilities(String projectName, String version, 
Severity severity) throws IntegrationException {
List<ComponentVulnerabilityView> vulnerabilities = getProjectVulnerabilities(projectName, version);
return vulnerabilities.stream()
.filter(vuln -> vuln.getSeverity() == severity)
.map(ComponentVulnerabilityView::getComponent)
.distinct()
.collect(Collectors.toList());
}
private int calculateRiskScore(ComponentView component) {
int score = 0;
// Add score based on vulnerability count and severity
if (component.getVulnerabilities() != null) {
for (ComponentVulnerabilityView vuln : component.getVulnerabilities()) {
switch (vuln.getSeverity()) {
case CRITICAL: score += 10; break;
case HIGH: score += 5; break;
case MEDIUM: score += 3; break;
case LOW: score += 1; break;
}
}
}
// Add score based on license risk
if (component.getLicenses() != null) {
for (LicenseView license : component.getLicenses()) {
if (isHighRiskLicense(license.getName())) {
score += 5;
}
}
}
return score;
}
private RiskPriority getRiskPriority(int riskScore) {
if (riskScore >= 10) return RiskPriority.HIGH;
if (riskScore >= 5) return RiskPriority.MEDIUM;
return RiskPriority.LOW;
}
private boolean isHighRiskLicense(String licenseName) {
Set<String> highRiskLicenses = Set.of(
"GPL-2.0", "GPL-3.0", "AGPL-3.0", "LGPL-2.1"
);
return highRiskLicenses.contains(licenseName);
}
public enum RiskPriority {
LOW, MEDIUM, HIGH
}
}

Configuration Class:

package com.example.security.blackduck;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "blackduck")
public class BlackDuckConfig {
private String url;
private String apiToken;
private boolean trustCert = true;
private int timeout = 300;
private String projectName;
private String projectVersion;
// Getters and setters
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getApiToken() { return apiToken; }
public void setApiToken(String apiToken) { this.apiToken = apiToken; }
public boolean isTrustCert() { return trustCert; }
public void setTrustCert(boolean trustCert) { this.trustCert = trustCert; }
public int getTimeout() { return timeout; }
public void setTimeout(int timeout) { this.timeout = timeout; }
public String getProjectName() { return projectName; }
public void setProjectName(String projectName) { this.projectName = projectName; }
public String getProjectVersion() { return projectVersion; }
public void setProjectVersion(String projectVersion) { this.projectVersion = projectVersion; }
}

3. Security Vulnerability Management

Vulnerability Analysis Service:

package com.example.security.blackduck;
import com.synopsys.integration.blackduck.api.generated.view.*;
import com.synopsys.integration.exception.IntegrationException;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class VulnerabilityAnalysisService {
private final BlackDuckService blackDuckService;
public VulnerabilityAnalysisService(BlackDuckService blackDuckService) {
this.blackDuckService = blackDuckService;
}
public VulnerabilityReport generateVulnerabilityReport(String projectName, String version) 
throws IntegrationException {
List<ComponentVulnerabilityView> vulnerabilities = 
blackDuckService.getProjectVulnerabilities(projectName, version);
VulnerabilityReport report = new VulnerabilityReport();
report.setProjectName(projectName);
report.setVersion(version);
report.setGeneratedAt(new Date());
report.setTotalVulnerabilities(vulnerabilities.size());
// Group by severity
Map<Severity, List<ComponentVulnerabilityView>> bySeverity = vulnerabilities.stream()
.collect(Collectors.groupingBy(ComponentVulnerabilityView::getSeverity));
report.setCriticalVulnerabilities(bySeverity.getOrDefault(Severity.CRITICAL, List.of()).size());
report.setHighVulnerabilities(bySeverity.getOrDefault(Severity.HIGH, List.of()).size());
report.setMediumVulnerabilities(bySeverity.getOrDefault(Severity.MEDIUM, List.of()).size());
report.setLowVulnerabilities(bySeverity.getOrDefault(Severity.LOW, List.of()).size());
// Get top vulnerable components
report.setTopVulnerableComponents(getTopVulnerableComponents(vulnerabilities));
// Get vulnerability trends
report.setVulnerabilityTrends(calculateTrends(projectName, version));
return report;
}
public List<RemediationAdvice> getRemediationAdvice(String projectName, String version) 
throws IntegrationException {
List<ComponentVulnerabilityView> vulnerabilities = 
blackDuckService.getProjectVulnerabilities(projectName, version);
return vulnerabilities.stream()
.map(this::createRemediationAdvice)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
public boolean hasCriticalVulnerabilities(String projectName, String version) 
throws IntegrationException {
List<ComponentVulnerabilityView> vulnerabilities = 
blackDuckService.getProjectVulnerabilities(projectName, version);
return vulnerabilities.stream()
.anyMatch(vuln -> vuln.getSeverity() == Severity.CRITICAL);
}
public List<String> getComponentsWithCriticalVulnerabilities(String projectName, String version) 
throws IntegrationException {
return blackDuckService.getComponentsWithVulnerabilities(projectName, version, Severity.CRITICAL)
.stream()
.map(ComponentView::getComponentName)
.collect(Collectors.toList());
}
private List<VulnerableComponent> getTopVulnerableComponents(
List<ComponentVulnerabilityView> vulnerabilities) {
Map<String, List<ComponentVulnerabilityView>> byComponent = vulnerabilities.stream()
.collect(Collectors.groupingBy(vuln -> vuln.getComponent().getComponentName()));
return byComponent.entrySet().stream()
.map(entry -> {
VulnerableComponent vc = new VulnerableComponent();
vc.setComponentName(entry.getKey());
vc.setVulnerabilityCount(entry.getValue().size());
vc.setHighestSeverity(getHighestSeverity(entry.getValue()));
return vc;
})
.sorted(Comparator.comparing(VulnerableComponent::getVulnerabilityCount).reversed())
.limit(10)
.collect(Collectors.toList());
}
private RemediationAdvice createRemediationAdvice(ComponentVulnerabilityView vulnerability) {
RemediationAdvice advice = new RemediationAdvice();
advice.setComponentName(vulnerability.getComponent().getComponentName());
advice.setVulnerabilityId(vulnerability.getVulnerabilityName());
advice.setSeverity(vulnerability.getSeverity());
advice.setDescription(vulnerability.getDescription());
// Add remediation suggestions based on vulnerability type
advice.setRemediationSteps(getRemediationSteps(vulnerability));
return advice;
}
private List<String> getRemediationSteps(ComponentVulnerabilityView vulnerability) {
List<String> steps = new ArrayList<>();
steps.add("Update to the latest patched version of the component");
steps.add("Review the component's security advisories");
steps.add("Consider alternative components if update is not available");
// Add specific steps based on vulnerability
if (vulnerability.getVulnerabilityName().contains("SQL")) {
steps.add("Implement parameterized queries");
steps.add("Use ORM with built-in SQL injection protection");
}
if (vulnerability.getVulnerabilityName().contains("XSS")) {
steps.add("Implement output encoding");
steps.add("Use Content Security Policy headers");
}
return steps;
}
private Severity getHighestSeverity(List<ComponentVulnerabilityView> vulnerabilities) {
return vulnerabilities.stream()
.map(ComponentVulnerabilityView::getSeverity)
.max(Comparator.naturalOrder())
.orElse(Severity.LOW);
}
private Map<String, Integer> calculateTrends(String projectName, String version) {
// Implementation to compare with previous scans
// This would typically query historical data from Black Duck
return Map.of(
"last_week", 5,
"last_month", 15,
"last_quarter", 45
);
}
}
// Report classes
class VulnerabilityReport {
private String projectName;
private String version;
private Date generatedAt;
private int totalVulnerabilities;
private int criticalVulnerabilities;
private int highVulnerabilities;
private int mediumVulnerabilities;
private int lowVulnerabilities;
private List<VulnerableComponent> topVulnerableComponents;
private Map<String, Integer> vulnerabilityTrends;
// Getters and setters
public String getProjectName() { return projectName; }
public void setProjectName(String projectName) { this.projectName = projectName; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
public Date getGeneratedAt() { return generatedAt; }
public void setGeneratedAt(Date generatedAt) { this.generatedAt = generatedAt; }
public int getTotalVulnerabilities() { return totalVulnerabilities; }
public void setTotalVulnerabilities(int totalVulnerabilities) { this.totalVulnerabilities = totalVulnerabilities; }
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 List<VulnerableComponent> getTopVulnerableComponents() { return topVulnerableComponents; }
public void setTopVulnerableComponents(List<VulnerableComponent> topVulnerableComponents) { this.topVulnerableComponents = topVulnerableComponents; }
public Map<String, Integer> getVulnerabilityTrends() { return vulnerabilityTrends; }
public void setVulnerabilityTrends(Map<String, Integer> vulnerabilityTrends) { this.vulnerabilityTrends = vulnerabilityTrends; }
}
class VulnerableComponent {
private String componentName;
private int vulnerabilityCount;
private Severity highestSeverity;
// Getters and setters
public String getComponentName() { return componentName; }
public void setComponentName(String componentName) { this.componentName = componentName; }
public int getVulnerabilityCount() { return vulnerabilityCount; }
public void setVulnerabilityCount(int vulnerabilityCount) { this.vulnerabilityCount = vulnerabilityCount; }
public Severity getHighestSeverity() { return highestSeverity; }
public void setHighestSeverity(Severity highestSeverity) { this.highestSeverity = highestSeverity; }
}
class RemediationAdvice {
private String componentName;
private String vulnerabilityId;
private Severity severity;
private String description;
private List<String> remediationSteps;
// Getters and setters
public String getComponentName() { return componentName; }
public void setComponentName(String componentName) { this.componentName = componentName; }
public String getVulnerabilityId() { return vulnerabilityId; }
public void setVulnerabilityId(String vulnerabilityId) { this.vulnerabilityId = vulnerabilityId; }
public Severity getSeverity() { return severity; }
public void setSeverity(Severity severity) { this.severity = severity; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public List<String> getRemediationSteps() { return remediationSteps; }
public void setRemediationSteps(List<String> remediationSteps) { this.remediationSteps = remediationSteps; }
}

4. License Compliance Management

License Compliance Service:

package com.example.security.blackduck;
import com.synopsys.integration.blackduck.api.generated.view.*;
import com.synopsys.integration.exception.IntegrationException;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class LicenseComplianceService {
private final BlackDuckService blackDuckService;
public LicenseComplianceService(BlackDuckService blackDuckService) {
this.blackDuckService = blackDuckService;
}
public LicenseComplianceReport checkLicenseCompliance(String projectName, String version) 
throws IntegrationException {
Optional<ProjectView> project = blackDuckService.getProjectService()
.getProjectByName(projectName);
Optional<ProjectVersionView> projectVersion = blackDuckService.getProjectService()
.getProjectVersion(project.get(), version);
List<ComponentView> components = blackDuckService.getComponentService()
.getAllComponents(projectVersion.get());
LicenseComplianceReport report = new LicenseComplianceReport();
report.setProjectName(projectName);
report.setVersion(version);
report.setGeneratedAt(new Date());
Map<LicenseRisk, List<ComponentView>> componentsByRisk = 
blackDuckService.getComponentsByRisk(projectName, version);
report.setHighRiskComponents(componentsByRisk.getOrDefault(BlackDuckService.RiskPriority.HIGH, List.of()));
report.setMediumRiskComponents(componentsByRisk.getOrDefault(BlackDuckService.RiskPriority.MEDIUM, List.of()));
report.setLowRiskComponents(componentsByRisk.getOrDefault(BlackDuckService.RiskPriority.LOW, List.of()));
report.setLicenseDistribution(calculateLicenseDistribution(components));
report.setForbiddenLicenses(findForbiddenLicenses(components));
return report;
}
public List<LicenseViolation> findLicenseViolations(String projectName, String version) 
throws IntegrationException {
PolicyStatusView policyStatus = blackDuckService.getPolicyStatus(projectName, version);
List<LicenseViolation> violations = new ArrayList<>();
// Convert policy violations to license violations
if (policyStatus.getComponentVersionStatusCounts() != null) {
for (ComponentVersionStatusCountView statusCount : policyStatus.getComponentVersionStatusCounts()) {
if (statusCount.getStatus() == PolicyStatusType.IN_VIOLATION) {
LicenseViolation violation = new LicenseViolation();
violation.setComponentName(statusCount.getName());
violation.setViolationType("LICENSE");
violation.setDescription("Component uses forbidden license");
violations.add(violation);
}
}
}
return violations;
}
public boolean hasLicenseViolations(String projectName, String version) throws IntegrationException {
PolicyStatusView policyStatus = blackDuckService.getPolicyStatus(projectName, version);
return policyStatus.getOverallStatus() == PolicyStatusType.IN_VIOLATION;
}
public Set<String> getForbiddenLicenses() {
return Set.of(
"GPL-2.0", "GPL-3.0", "AGPL-3.0", 
"LGPL-2.1", "SSPL-1.0"
);
}
public Set<String> getApprovedLicenses() {
return Set.of(
"Apache-2.0", "MIT", "BSD-2-Clause", "BSD-3-Clause",
"ISC", "EPL-2.0", "CDDL-1.0", "MPL-2.0"
);
}
private Map<String, Integer> calculateLicenseDistribution(List<ComponentView> components) {
return components.stream()
.flatMap(component -> component.getLicenses().stream())
.collect(Collectors.groupingBy(
LicenseView::getName,
Collectors.summingInt(license -> 1)
));
}
private List<ComponentView> findForbiddenLicenses(List<ComponentView> components) {
Set<String> forbiddenLicenses = getForbiddenLicenses();
return components.stream()
.filter(component -> component.getLicenses().stream()
.anyMatch(license -> forbiddenLicenses.contains(license.getName())))
.collect(Collectors.toList());
}
}
class LicenseComplianceReport {
private String projectName;
private String version;
private Date generatedAt;
private List<ComponentView> highRiskComponents;
private List<ComponentView> mediumRiskComponents;
private List<ComponentView> lowRiskComponents;
private Map<String, Integer> licenseDistribution;
private List<ComponentView> forbiddenLicenses;
// Getters and setters
public String getProjectName() { return projectName; }
public void setProjectName(String projectName) { this.projectName = projectName; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
public Date getGeneratedAt() { return generatedAt; }
public void setGeneratedAt(Date generatedAt) { this.generatedAt = generatedAt; }
public List<ComponentView> getHighRiskComponents() { return highRiskComponents; }
public void setHighRiskComponents(List<ComponentView> highRiskComponents) { this.highRiskComponents = highRiskComponents; }
public List<ComponentView> getMediumRiskComponents() { return mediumRiskComponents; }
public void setMediumRiskComponents(List<ComponentView> mediumRiskComponents) { this.mediumRiskComponents = mediumRiskComponents; }
public List<ComponentView> getLowRiskComponents() { return lowRiskComponents; }
public void setLowRiskComponents(List<ComponentView> lowRiskComponents) { this.lowRiskComponents = lowRiskComponents; }
public Map<String, Integer> getLicenseDistribution() { return licenseDistribution; }
public void setLicenseDistribution(Map<String, Integer> licenseDistribution) { this.licenseDistribution = licenseDistribution; }
public List<ComponentView> getForbiddenLicenses() { return forbiddenLicenses; }
public void setForbiddenLicenses(List<ComponentView> forbiddenLicenses) { this.forbiddenLicenses = forbiddenLicenses; }
}
class LicenseViolation {
private String componentName;
private String violationType;
private String description;
private Severity severity;
// Getters and setters
public String getComponentName() { return componentName; }
public void setComponentName(String componentName) { this.componentName = componentName; }
public String getViolationType() { return violationType; }
public void setViolationType(String violationType) { this.violationType = violationType; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public Severity getSeverity() { return severity; }
public void setSeverity(Severity severity) { this.severity = severity; }
}

5. CI/CD Integration

Jenkins Pipeline:

pipeline {
agent any
tools {
jdk 'jdk17'
maven 'maven-3.8'
}
environment {
BLACKDUCK_URL = 'https://your-blackduck-server.com'
BLACKDUCK_API_TOKEN = credentials('blackduck-api-token')
DETECT_VERSION = '8.9.0'
}
stages {
stage('Build') {
steps {
sh 'mvn clean compile'
}
}
stage('Black Duck Scan') {
steps {
script {
// Download Detect
sh "curl -sSL https://detect.synopsys.com/detect${DETECT_VERSION}.sh -o detect.sh"
sh "chmod +x detect.sh"
// Run Black Duck Scan
sh """
./detect.sh \
--blackduck.url=${BLACKDUCK_URL} \
--blackduck.api.token=${BLACKDUCK_API_TOKEN} \
--detect.project.name=${env.JOB_NAME} \
--detect.project.version=${env.BUILD_NUMBER} \
--detect.source.path=. \
--detect.tools=DETECTOR \
--detect.policy.check.fail.on.severities=BLOCKER,CRITICAL \
--detect.risk.check.fail.on.severities=BLOCKER,CRITICAL \
--logging.level.com.synopsys.integration=DEBUG
"""
}
}
post {
always {
// Archive results
archiveArtifacts artifacts: 'runs/**/*.json', allowEmptyArchive: true
archiveArtifacts artifacts: 'runs/**/*.html', allowEmptyArchive: true
// Publish HTML report
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'runs',
reportFiles: 'report.html',
reportName: 'Black Duck Report'
])
}
}
}
stage('Security Gate') {
steps {
script {
// Check for policy violations
def policyCheck = sh(
script: """
./detect.sh \
--blackduck.url=${BLACKDUCK_URL} \
--blackduck.api.token=${BLACKDUCK_API_TOKEN} \
--detect.policy.check.fail.on.severities=BLOCKER,CRITICAL \
--detect.policy.check=true \
--detect.risk.check.policy.categories=LICENSE,SECURITY \
--detect.tools=DETECTOR \
--detect.source.path=.
""",
returnStatus: true
)
if (policyCheck != 0) {
error "Black Duck policy check failed. Check the report for details."
}
}
}
}
stage('Generate SBOM') {
steps {
script {
// Generate Software Bill of Materials
sh """
./detect.sh \
--blackduck.url=${BLACKDUCK_URL} \
--blackduck.api.token=${BLACKDUCK_API_TOKEN} \
--detect.project.name=${env.JOB_NAME} \
--detect.project.version=${env.BUILD_NUMBER} \
--detect.tools=BOM \
--detect.bom.aggregate.name=${env.JOB_NAME}-${env.BUILD_NUMBER} \
--detect.bom.aggregate.version=1.0
"""
// Archive SBOM
archiveArtifacts artifacts: '**/*.spdx.json', allowEmptyArchive: true
archiveArtifacts artifacts: '**/*.cyclonedx.json', allowEmptyArchive: true
}
}
}
}
post {
always {
// Clean up
sh 'rm -f detect.sh'
sh 'rm -rf runs/'
}
failure {
// Notify on failure
emailext (
subject: "Black Duck Scan Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
body: "Black Duck security scan detected policy violations or critical vulnerabilities.",
to: "[email protected]"
)
}
success {
// Notify on success
emailext (
subject: "Black Duck Scan Passed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
body: "Black Duck security scan completed successfully with no critical issues.",
to: "[email protected]"
)
}
}
}

GitHub Actions Workflow:

name: Black Duck Security Scan
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
schedule:
- cron: '0 2 * * 1'  # Weekly on Monday at 2 AM
jobs:
blackduck-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: 'maven'
- name: Build project
run: mvn clean compile -DskipTests
- name: Run Black Duck Detect
uses: synopsys-sig/detect-action@v1
with:
blackduck-url: ${{ secrets.BLACKDUCK_URL }}
blackduck-api-token: ${{ secrets.BLACKDUCK_API_TOKEN }}
project-name: ${{ github.event.repository.name }}
version-name: ${{ github.sha }}
failure-policy-severities: 'BLOCKER,CRITICAL'
scan-mode: 'INTELLIGENT'
tools: 'DETECTOR'
additional-args: '--detect.risk.check.policy.categories=LICENSE,SECURITY --detect.policy.check.fail.on.severities=BLOCKER,CRITICAL'
- name: Upload Black Duck Report
uses: actions/upload-artifact@v3
with:
name: blackduck-report
path: runs/
retention-days: 30
- name: Check for Critical Vulnerabilities
run: |
if [ -f "runs/risk-report.html" ]; then
echo "Black Duck scan completed"
else
echo "Black Duck scan failed"
exit 1
fi

6. Spring Boot Configuration

application.yml:

blackduck:
url: ${BLACKDUCK_URL:https://your-blackduck-server.com}
api-token: ${BLACKDUCK_API_TOKEN}
trust-cert: true
timeout: 300
project-name: ${spring.application.name}
project-version: ${app.version:1.0.0}
security:
blackduck:
enabled: true
scan-on-startup: false
fail-on-critical: true
report-dir: ./blackduck-reports
policies:
forbidden-licenses: GPL-2.0, GPL-3.0, AGPL-3.0
critical-severity: BLOCKER, CRITICAL
auto-remediate: false
app:
version: 1.0.0

Configuration Class:

package com.example.security.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BlackDuckAutoConfiguration {
@Bean
@ConfigurationProperties(prefix = "security.blackduck")
public BlackDuckSecurityProperties blackDuckSecurityProperties() {
return new BlackDuckSecurityProperties();
}
}
@ConfigurationProperties(prefix = "security.blackduck")
public class BlackDuckSecurityProperties {
private boolean enabled = true;
private boolean scanOnStartup = false;
private boolean failOnCritical = true;
private String reportDir = "./blackduck-reports";
private BlackDuckPolicies policies = new BlackDuckPolicies();
// Getters and setters
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public boolean isScanOnStartup() { return scanOnStartup; }
public void setScanOnStartup(boolean scanOnStartup) { this.scanOnStartup = scanOnStartup; }
public boolean isFailOnCritical() { return failOnCritical; }
public void setFailOnCritical(boolean failOnCritical) { this.failOnCritical = failOnCritical; }
public String getReportDir() { return reportDir; }
public void setReportDir(String reportDir) { this.reportDir = reportDir; }
public BlackDuckPolicies getPolicies() { return policies; }
public void setPolicies(BlackDuckPolicies policies) { this.policies = policies; }
public static class BlackDuckPolicies {
private String forbiddenLicenses = "GPL-2.0,GPL-3.0,AGPL-3.0";
private String criticalSeverity = "BLOCKER,CRITICAL";
private boolean autoRemediate = false;
// Getters and setters
public String getForbiddenLicenses() { return forbiddenLicenses; }
public void setForbiddenLicenses(String forbiddenLicenses) { this.forbiddenLicenses = forbiddenLicenses; }
public String getCriticalSeverity() { return criticalSeverity; }
public void setCriticalSeverity(String criticalSeverity) { this.criticalSeverity = criticalSeverity; }
public boolean isAutoRemediate() { return autoRemediate; }
public void setAutoRemediate(boolean autoRemediate) { this.autoRemediate = autoRemediate; }
}
}

7. REST API Controllers

Black Duck REST Controller:

package com.example.security.controller;
import com.example.security.blackduck.*;
import com.synopsys.integration.exception.IntegrationException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/security/blackduck")
public class BlackDuckController {
private final BlackDuckService blackDuckService;
private final VulnerabilityAnalysisService vulnerabilityService;
private final LicenseComplianceService licenseService;
public BlackDuckController(BlackDuckService blackDuckService,
VulnerabilityAnalysisService vulnerabilityService,
LicenseComplianceService licenseService) {
this.blackDuckService = blackDuckService;
this.vulnerabilityService = vulnerabilityService;
this.licenseService = licenseService;
}
@GetMapping("/projects/{projectName}/versions/{version}/vulnerabilities")
public ResponseEntity<VulnerabilityReport> getVulnerabilities(
@PathVariable String projectName,
@PathVariable String version) throws IntegrationException {
VulnerabilityReport report = vulnerabilityService.generateVulnerabilityReport(projectName, version);
return ResponseEntity.ok(report);
}
@GetMapping("/projects/{projectName}/versions/{version}/remediation")
public ResponseEntity<List<RemediationAdvice>> getRemediationAdvice(
@PathVariable String projectName,
@PathVariable String version) throws IntegrationException {
List<RemediationAdvice> advice = vulnerabilityService.getRemediationAdvice(projectName, version);
return ResponseEntity.ok(advice);
}
@GetMapping("/projects/{projectName}/versions/{version}/licenses")
public ResponseEntity<LicenseComplianceReport> getLicenseCompliance(
@PathVariable String projectName,
@PathVariable String version) throws IntegrationException {
LicenseComplianceReport report = licenseService.checkLicenseCompliance(projectName, version);
return ResponseEntity.ok(report);
}
@GetMapping("/projects/{projectName}/versions/{version}/policy-status")
public ResponseEntity<Map<String, Object>> getPolicyStatus(
@PathVariable String projectName,
@PathVariable String version) throws IntegrationException {
boolean hasViolations = licenseService.hasLicenseViolations(projectName, version);
boolean hasCriticalVulns = vulnerabilityService.hasCriticalVulnerabilities(projectName, version);
Map<String, Object> status = Map.of(
"projectName", projectName,
"version", version,
"policyViolations", hasViolations,
"criticalVulnerabilities", hasCriticalVulns,
"overallStatus", (!hasViolations && !hasCriticalVulns) ? "PASS" : "FAIL"
);
return ResponseEntity.ok(status);
}
@PostMapping("/projects/{projectName}/versions/{version}/scan")
public ResponseEntity<Map<String, String>> triggerScan(
@PathVariable String projectName,
@PathVariable String version) {
// Implementation to trigger a new scan
// This would typically call the Detect CLI or API
Map<String, String> response = Map.of(
"status", "scan_triggered",
"project", projectName,
"version", version,
"message", "Black Duck scan has been triggered"
);
return ResponseEntity.accepted().body(response);
}
}

8. Best Practices and Tips

Performance Optimization:

# detect-performance.properties
detect.parallel.processors=4
detect.parallel.processor.timeout=120
detect.memory=4096
detect.detector.search.depth=5
detect.detector.search.continue=false
detect.detector.excluded.directories=build,target,node_modules,.gradle,.idea
detect.binary.scan.file.size.limit=50000000

Security Hardening:

# detect-security.properties
detect.api.timeout=300
detect.trust.cert=false
detect.cleanup=true
detect.retry.count=3
detect.policy.check.fail.on.severities=BLOCKER,CRITICAL,HIGH
detect.risk.check.fail.on.severities=BLOCKER,CRITICAL

Conclusion

Black Duck Hub provides comprehensive software composition analysis for Java applications. Key benefits include:

  1. Vulnerability Management: Identifies and prioritizes security vulnerabilities
  2. License Compliance: Manages open-source license obligations and risks
  3. SBOM Generation: Creates detailed software bill of materials
  4. Policy Enforcement: Enforces organizational security policies
  5. CI/CD Integration: Seamless integration with development pipelines
  6. Remediation Guidance: Provides actionable fix recommendations

By implementing the configurations and patterns shown above, you can establish a robust software security program that identifies risks early, ensures compliance, and maintains the security posture of your Java applications throughout their lifecycle.

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