Introduction to Black Duck Hub
Black Duck Hub (now part of Synopsys) is a comprehensive Software Composition Analysis (SCA) tool that helps identify and manage open-source security vulnerabilities, license compliance risks, and operational risks in your software supply chain.
Key Features
- Open Source Detection: Automatically discovers open-source components
- Vulnerability Scanning: Identifies known security vulnerabilities
- License Compliance: Manages open-source license obligations
- Policy Management: Enforces security and license policies
- Integration: REST API for automation and CI/CD integration
Implementation Guide
Dependencies
Add to your pom.xml:
<properties>
<blackduck.version>2023.4.0</blackduck.version>
<jackson.version>2.15.2</jackson.version>
<okhttp.version>4.11.0</okhttp.version>
</properties>
<dependencies>
<!-- Black Duck Hub REST Client -->
<dependency>
<groupId>com.synopsys.integration</groupId>
<artifactId>blackduck-common</artifactId>
<version>${blackduck.version}</version>
</dependency>
<dependency>
<groupId>com.synopsys.integration</groupId>
<artifactId>blackduck-api</artifactId>
<version>${blackduck.version}</version>
</dependency>
<dependency>
<groupId>com.synopsys.integration</groupId>
<artifactId>blackduck-http-client</artifactId>
<version>${blackduck.version}</version>
</dependency>
<!-- HTTP Client -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<!-- For file operations -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.13.0</version>
</dependency>
</dependencies>
Core Black Duck Hub Integration
Configuration and Client Setup
package com.example.blackduck;
import com.synopsys.integration.blackduck.api.generated.discovery.ApiDiscovery;
import com.synopsys.integration.blackduck.configuration.BlackDuckServerConfig;
import com.synopsys.integration.blackduck.configuration.BlackDuckServerConfigBuilder;
import com.synopsys.integration.blackduck.service.BlackDuckApiClient;
import com.synopsys.integration.blackduck.service.BlackDuckServicesFactory;
import com.synopsys.integration.exception.IntegrationException;
import com.synopsys.integration.log.Slf4jIntLogger;
import com.synopsys.integration.rest.client.IntHttpClient;
import com.synopsys.integration.rest.credentials.Credentials;
import com.synopsys.integration.rest.credentials.CredentialsBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URL;
import java.util.Optional;
public class BlackDuckClient {
private static final Logger logger = LoggerFactory.getLogger(BlackDuckClient.class);
private final BlackDuckServicesFactory blackDuckServicesFactory;
private final BlackDuckApiClient blackDuckApiClient;
public BlackDuckClient(String hubUrl, String apiToken, int timeoutSeconds) throws IntegrationException {
BlackDuckServerConfigBuilder configBuilder = new BlackDuckServerConfigBuilder();
configBuilder.setUrl(hubUrl);
configBuilder.setApiToken(apiToken);
configBuilder.setTimeout(timeoutSeconds);
configBuilder.setTrustCert(true); // Only for testing
BlackDuckServerConfig blackDuckServerConfig = configBuilder.build();
Slf4jIntLogger intLogger = new Slf4jIntLogger(logger);
this.blackDuckServicesFactory = blackDuckServerConfig.createBlackDuckServicesFactory(intLogger);
this.blackDuckApiClient = blackDuckServicesFactory.getBlackDuckApiClient();
logger.info("Black Duck Hub client initialized for: {}", hubUrl);
}
public BlackDuckClient(String hubUrl, String username, String password, int timeoutSeconds)
throws IntegrationException {
BlackDuckServerConfigBuilder configBuilder = new BlackDuckServerConfigBuilder();
configBuilder.setUrl(hubUrl);
CredentialsBuilder credentialsBuilder = new CredentialsBuilder();
credentialsBuilder.setUsername(username);
credentialsBuilder.setPassword(password);
Credentials credentials = credentialsBuilder.build();
configBuilder.setCredentials(credentials);
configBuilder.setTimeout(timeoutSeconds);
configBuilder.setTrustCert(true); // Only for testing
BlackDuckServerConfig blackDuckServerConfig = configBuilder.build();
Slf4jIntLogger intLogger = new Slf4jIntLogger(logger);
this.blackDuckServicesFactory = blackDuckServerConfig.createBlackDuckServicesFactory(intLogger);
this.blackDuckApiClient = blackDuckServicesFactory.getBlackDuckApiClient();
logger.info("Black Duck Hub client initialized for: {}", hubUrl);
}
public boolean testConnection() {
try {
ApiDiscovery apiDiscovery = new ApiDiscovery(blackDuckApiClient);
apiDiscovery.getCurrentVersion();
logger.info("Black Duck Hub connection test successful");
return true;
} catch (IntegrationException e) {
logger.error("Black Duck Hub connection test failed", e);
return false;
}
}
public BlackDuckServicesFactory getServicesFactory() {
return blackDuckServicesFactory;
}
public BlackDuckApiClient getApiClient() {
return blackDuckApiClient;
}
public void close() {
if (blackDuckApiClient != null) {
try {
blackDuckApiClient.close();
} catch (IOException e) {
logger.warn("Error closing Black Duck client", e);
}
}
}
}
Project Management
package com.example.blackduck.projects;
import com.synopsys.integration.blackduck.api.generated.component.ProjectRequest;
import com.synopsys.integration.blackduck.api.generated.view.ProjectView;
import com.synopsys.integration.blackduck.service.BlackDuckApiClient;
import com.synopsys.integration.blackduck.service.dataservice.ProjectService;
import com.synopsys.integration.blackduck.service.model.ProjectVersionWrapper;
import com.synopsys.integration.exception.IntegrationException;
import com.synopsys.integration.rest.HttpUrl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Optional;
public class ProjectManager {
private static final Logger logger = LoggerFactory.getLogger(ProjectManager.class);
private final ProjectService projectService;
private final BlackDuckApiClient blackDuckApiClient;
public ProjectManager(ProjectService projectService, BlackDuckApiClient blackDuckApiClient) {
this.projectService = projectService;
this.blackDuckApiClient = blackDuckApiClient;
}
public ProjectVersionWrapper createProject(String projectName, String versionName,
String description) throws IntegrationException {
logger.info("Creating project: {} version: {}", projectName, versionName);
ProjectRequest projectRequest = new ProjectRequest();
projectRequest.setName(projectName);
projectRequest.setDescription(description);
projectRequest.setProjectLevelAdjustments(true);
projectRequest.setVersion(versionName);
ProjectVersionWrapper projectVersionWrapper = projectService.createProject(projectRequest);
logger.info("Successfully created project: {} with version: {}",
projectName, versionName);
return projectVersionWrapper;
}
public Optional<ProjectVersionWrapper> getProject(String projectName, String versionName)
throws IntegrationException {
logger.debug("Looking up project: {} version: {}", projectName, versionName);
return projectService.getProjectVersion(projectName, versionName);
}
public List<ProjectView> getAllProjects() throws IntegrationException {
logger.debug("Retrieving all projects");
return projectService.getAllProjects();
}
public ProjectVersionWrapper getOrCreateProject(String projectName, String versionName,
String description) throws IntegrationException {
Optional<ProjectVersionWrapper> existingProject = getProject(projectName, versionName);
if (existingProject.isPresent()) {
logger.info("Found existing project: {} version: {}", projectName, versionName);
return existingProject.get();
} else {
logger.info("Project not found, creating new: {} version: {}", projectName, versionName);
return createProject(projectName, versionName, description);
}
}
public boolean deleteProject(String projectName, String versionName) throws IntegrationException {
Optional<ProjectVersionWrapper> projectWrapper = getProject(projectName, versionName);
if (projectWrapper.isPresent()) {
ProjectVersionWrapper wrapper = projectWrapper.get();
HttpUrl projectVersionUrl = wrapper.getProjectVersionView().getHref();
blackDuckApiClient.delete(projectVersionUrl);
logger.info("Successfully deleted project: {} version: {}", projectName, versionName);
return true;
} else {
logger.warn("Project not found for deletion: {} version: {}", projectName, versionName);
return false;
}
}
public void updateProjectDescription(String projectName, String versionName,
String newDescription) throws IntegrationException {
Optional<ProjectVersionWrapper> projectWrapper = getProject(projectName, versionName);
if (projectWrapper.isPresent()) {
ProjectVersionWrapper wrapper = projectWrapper.get();
ProjectView projectView = wrapper.getProjectView();
projectView.setDescription(newDescription);
blackDuckApiClient.put(projectView);
logger.info("Updated project description for: {} version: {}", projectName, versionName);
} else {
logger.warn("Project not found for update: {} version: {}", projectName, versionName);
}
}
}
Vulnerability and Component Analysis
Component Scanner
package com.example.blackduck.scan;
import com.synopsys.integration.blackduck.api.generated.component.ProjectVersionComponentRequest;
import com.synopsys.integration.blackduck.api.generated.view.ComponentSearchResultView;
import com.synopsys.integration.blackduck.api.generated.view.ProjectVersionComponentView;
import com.synopsys.integration.blackduck.api.generated.view.VulnerabilityView;
import com.synopsys.integration.blackduck.service.BlackDuckApiClient;
import com.synopsys.integration.blackduck.service.dataservice.ComponentService;
import com.synopsys.integration.blackduck.service.dataservice.ProjectBomService;
import com.synopsys.integration.blackduck.service.model.ComponentVersion;
import com.synopsys.integration.blackduck.service.model.ProjectVersionWrapper;
import com.synopsys.integration.exception.IntegrationException;
import com.synopsys.integration.rest.HttpUrl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
public class ComponentScanner {
private static final Logger logger = LoggerFactory.getLogger(ComponentScanner.class);
private final ComponentService componentService;
private final ProjectBomService projectBomService;
private final BlackDuckApiClient blackDuckApiClient;
public ComponentScanner(ComponentService componentService, ProjectBomService projectBomService,
BlackDuckApiClient blackDuckApiClient) {
this.componentService = componentService;
this.projectBomService = projectBomService;
this.blackDuckApiClient = blackDuckApiClient;
}
public List<ComponentSearchResultView> searchComponent(String componentName, String version)
throws IntegrationException {
logger.debug("Searching for component: {} version: {}", componentName, version);
return componentService.getAllComponentSearchResults(componentName, version);
}
public ComponentVersion getComponentVersion(String componentName, String version)
throws IntegrationException {
logger.debug("Getting component version: {} version: {}", componentName, version);
List<ComponentSearchResultView> searchResults = searchComponent(componentName, version);
if (searchResults.isEmpty()) {
logger.warn("Component not found: {} version: {}", componentName, version);
return null;
}
// Get the first result (most relevant)
ComponentSearchResultView searchResult = searchResults.get(0);
HttpUrl componentVersionUrl = searchResult.getComponentVersion();
return componentService.getComponentVersion(componentVersionUrl);
}
public List<VulnerabilityView> getComponentVulnerabilities(String componentName, String version)
throws IntegrationException {
logger.debug("Getting vulnerabilities for component: {} version: {}", componentName, version);
ComponentVersion componentVersion = getComponentVersion(componentName, version);
if (componentVersion == null) {
return Collections.emptyList();
}
return componentService.getVulnerabilitiesForComponentVersion(componentVersion);
}
public void addComponentToProject(ProjectVersionWrapper projectWrapper,
String componentName, String componentVersion,
String usage) throws IntegrationException {
logger.info("Adding component {}:{} to project", componentName, componentVersion);
ComponentVersion component = getComponentVersion(componentName, componentVersion);
if (component == null) {
logger.error("Component not found: {}:{}", componentName, componentVersion);
throw new IllegalArgumentException("Component not found: " + componentName + ":" + componentVersion);
}
ProjectVersionComponentRequest componentRequest = new ProjectVersionComponentRequest();
componentRequest.setComponentVersion(component.getHref().string());
componentRequest.setComponentName(component.getComponentName());
componentRequest.setComponentVersionName(component.getVersionName());
componentRequest.setComponentUsage(usage != null ? usage : "DYNAMICALLY_LINKED");
projectBomService.addComponentToProjectVersion(componentRequest,
projectWrapper.getProjectVersionView());
logger.info("Successfully added component {}:{} to project", componentName, componentVersion);
}
public List<ProjectVersionComponentView> getProjectBom(ProjectVersionWrapper projectWrapper)
throws IntegrationException {
logger.debug("Getting BOM for project: {}",
projectWrapper.getProjectView().getName());
return projectBomService.getComponentsForProjectVersion(projectWrapper.getProjectVersionView());
}
public VulnerabilityReport generateVulnerabilityReport(ProjectVersionWrapper projectWrapper)
throws IntegrationException {
logger.info("Generating vulnerability report for project: {}",
projectWrapper.getProjectView().getName());
List<ProjectVersionComponentView> bomComponents = getProjectBom(projectWrapper);
VulnerabilityReport report = new VulnerabilityReport(projectWrapper);
for (ProjectVersionComponentView component : bomComponents) {
ComponentVulnerability componentVuln = analyzeComponentVulnerabilities(component);
report.addComponentVulnerability(componentVuln);
}
report.calculateSummary();
logger.info("Vulnerability report generated with {} components", bomComponents.size());
return report;
}
private ComponentVulnerability analyzeComponentVulnerabilities(ProjectVersionComponentView component)
throws IntegrationException {
ComponentVulnerability componentVuln = new ComponentVulnerability(
component.getComponentName(),
component.getComponentVersionName(),
component.getComponentVersion()
);
// Get vulnerability data
List<VulnerabilityView> vulnerabilities = componentService.getVulnerabilitiesForComponent(
component.getComponentVersion()
);
for (VulnerabilityView vulnerability : vulnerabilities) {
VulnerabilityDetail vulnDetail = new VulnerabilityDetail(
vulnerability.getName(),
vulnerability.getDescription(),
vulnerability.getBaseScore(),
vulnerability.getSeverity(),
vulnerability.getCweId(),
vulnerability.getPublishedDate(),
vulnerability.getUpdatedDate()
);
componentVuln.addVulnerability(vulnDetail);
}
return componentVuln;
}
public Map<String, Integer> getVulnerabilitySummary(ProjectVersionWrapper projectWrapper)
throws IntegrationException {
VulnerabilityReport report = generateVulnerabilityReport(projectWrapper);
return report.getVulnerabilitySummary();
}
}
Vulnerability Reporting
package com.example.blackduck.scan;
import com.synopsys.integration.blackduck.service.model.ProjectVersionWrapper;
import java.util.*;
public class VulnerabilityReport {
private final ProjectVersionWrapper projectWrapper;
private final List<ComponentVulnerability> componentVulnerabilities;
private final Map<String, Integer> vulnerabilitySummary;
private final Date reportDate;
public VulnerabilityReport(ProjectVersionWrapper projectWrapper) {
this.projectWrapper = projectWrapper;
this.componentVulnerabilities = new ArrayList<>();
this.vulnerabilitySummary = new HashMap<>();
this.reportDate = new Date();
}
public void addComponentVulnerability(ComponentVulnerability componentVuln) {
componentVulnerabilities.add(componentVuln);
}
public void calculateSummary() {
vulnerabilitySummary.clear();
for (ComponentVulnerability componentVuln : componentVulnerabilities) {
for (VulnerabilityDetail vuln : componentVuln.getVulnerabilities()) {
String severity = vuln.getSeverity();
vulnerabilitySummary.put(severity,
vulnerabilitySummary.getOrDefault(severity, 0) + 1);
}
}
}
public List<ComponentVulnerability> getCriticalComponents() {
List<ComponentVulnerability> criticalComponents = new ArrayList<>();
for (ComponentVulnerability component : componentVulnerabilities) {
if (component.hasCriticalVulnerabilities()) {
criticalComponents.add(component);
}
}
return criticalComponents;
}
public List<ComponentVulnerability> getComponentsWithVulnerabilities() {
List<ComponentVulnerability> componentsWithVulns = new ArrayList<>();
for (ComponentVulnerability component : componentVulnerabilities) {
if (component.hasVulnerabilities()) {
componentsWithVulns.add(component);
}
}
return componentsWithVulns;
}
// Getters
public ProjectVersionWrapper getProjectWrapper() { return projectWrapper; }
public List<ComponentVulnerability> getComponentVulnerabilities() { return componentVulnerabilities; }
public Map<String, Integer> getVulnerabilitySummary() { return vulnerabilitySummary; }
public Date getReportDate() { return reportDate; }
public int getTotalVulnerabilities() {
return vulnerabilitySummary.values().stream().mapToInt(Integer::intValue).sum();
}
public String getProjectName() {
return projectWrapper.getProjectView().getName();
}
public String getProjectVersion() {
return projectWrapper.getProjectVersionView().getVersionName();
}
}
class ComponentVulnerability {
private final String componentName;
private final String componentVersion;
private final String componentUrl;
private final List<VulnerabilityDetail> vulnerabilities;
public ComponentVulnerability(String componentName, String componentVersion, String componentUrl) {
this.componentName = componentName;
this.componentVersion = componentVersion;
this.componentUrl = componentUrl;
this.vulnerabilities = new ArrayList<>();
}
public void addVulnerability(VulnerabilityDetail vulnerability) {
vulnerabilities.add(vulnerability);
}
public boolean hasVulnerabilities() {
return !vulnerabilities.isEmpty();
}
public boolean hasCriticalVulnerabilities() {
return vulnerabilities.stream()
.anyMatch(vuln -> "CRITICAL".equalsIgnoreCase(vuln.getSeverity()));
}
public boolean hasHighVulnerabilities() {
return vulnerabilities.stream()
.anyMatch(vuln -> "HIGH".equalsIgnoreCase(vuln.getSeverity()));
}
public int getVulnerabilityCount() {
return vulnerabilities.size();
}
public int getVulnerabilityCountBySeverity(String severity) {
return (int) vulnerabilities.stream()
.filter(vuln -> severity.equalsIgnoreCase(vuln.getSeverity()))
.count();
}
// Getters
public String getComponentName() { return componentName; }
public String getComponentVersion() { return componentVersion; }
public String getComponentUrl() { return componentUrl; }
public List<VulnerabilityDetail> getVulnerabilities() { return vulnerabilities; }
}
class VulnerabilityDetail {
private final String name;
private final String description;
private final Double baseScore;
private final String severity;
private final String cweId;
private final Date publishedDate;
private final Date updatedDate;
public VulnerabilityDetail(String name, String description, Double baseScore,
String severity, String cweId, Date publishedDate, Date updatedDate) {
this.name = name;
this.description = description;
this.baseScore = baseScore;
this.severity = severity;
this.cweId = cweId;
this.publishedDate = publishedDate;
this.updatedDate = updatedDate;
}
// Getters
public String getName() { return name; }
public String getDescription() { return description; }
public Double getBaseScore() { return baseScore; }
public String getSeverity() { return severity; }
public String getCweId() { return cweId; }
public Date getPublishedDate() { return publishedDate; }
public Date getUpdatedDate() { return updatedDate; }
public boolean isCritical() {
return "CRITICAL".equalsIgnoreCase(severity);
}
public boolean isHigh() {
return "HIGH".equalsIgnoreCase(severity);
}
public boolean isMedium() {
return "MEDIUM".equalsIgnoreCase(severity);
}
public boolean isLow() {
return "LOW".equalsIgnoreCase(severity);
}
}
License Compliance Management
package com.example.blackduck.license;
import com.synopsys.integration.blackduck.api.generated.view.LicenseView;
import com.synopsys.integration.blackduck.api.generated.view.ProjectVersionComponentView;
import com.synopsys.integration.blackduck.service.BlackDuckApiClient;
import com.synopsys.integration.blackduck.service.dataservice.LicenseService;
import com.synopsys.integration.blackduck.service.dataservice.ProjectBomService;
import com.synopsys.integration.blackduck.service.model.LicenseDefinition;
import com.synopsys.integration.blackduck.service.model.ProjectVersionWrapper;
import com.synopsys.integration.exception.IntegrationException;
import com.synopsys.integration.rest.HttpUrl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
public class LicenseComplianceManager {
private static final Logger logger = LoggerFactory.getLogger(LicenseComplianceManager.class);
private final LicenseService licenseService;
private final ProjectBomService projectBomService;
private final BlackDuckApiClient blackDuckApiClient;
public LicenseComplianceManager(LicenseService licenseService, ProjectBomService projectBomService,
BlackDuckApiClient blackDuckApiClient) {
this.licenseService = licenseService;
this.projectBomService = projectBomService;
this.blackDuckApiClient = blackDuckApiClient;
}
public List<LicenseView> getAllLicenses() throws IntegrationException {
logger.debug("Retrieving all licenses");
return licenseService.getAllLicenses();
}
public Optional<LicenseView> getLicenseByName(String licenseName) throws IntegrationException {
logger.debug("Looking up license: {}", licenseName);
List<LicenseView> allLicenses = getAllLicenses();
return allLicenses.stream()
.filter(license -> licenseName.equals(license.getName()))
.findFirst();
}
public LicenseView getLicenseById(String licenseId) throws IntegrationException {
logger.debug("Looking up license by ID: {}", licenseId);
List<LicenseView> allLicenses = getAllLicenses();
return allLicenses.stream()
.filter(license -> licenseId.equals(license.getLicenseId()))
.findFirst()
.orElse(null);
}
public LicenseReport generateLicenseReport(ProjectVersionWrapper projectWrapper)
throws IntegrationException {
logger.info("Generating license report for project: {}",
projectWrapper.getProjectView().getName());
List<ProjectVersionComponentView> bomComponents =
projectBomService.getComponentsForProjectVersion(projectWrapper.getProjectVersionView());
LicenseReport report = new LicenseReport(projectWrapper);
for (ProjectVersionComponentView component : bomComponents) {
ComponentLicenseInfo licenseInfo = analyzeComponentLicenses(component);
report.addComponentLicense(licenseInfo);
}
report.calculateSummary();
logger.info("License report generated with {} components", bomComponents.size());
return report;
}
private ComponentLicenseInfo analyzeComponentLicenses(ProjectVersionComponentView component)
throws IntegrationException {
ComponentLicenseInfo licenseInfo = new ComponentLicenseInfo(
component.getComponentName(),
component.getComponentVersionName(),
component.getComponentVersion()
);
// Get license information
List<LicenseDefinition> licenses = componentService.getLicensesForComponent(
component.getComponentVersion()
);
for (LicenseDefinition license : licenses) {
LicenseDetail licenseDetail = new LicenseDetail(
license.getName(),
license.getLicenseId(),
license.getLicenseType(),
license.getLicenseFamilyName(),
license.getLicenseDisplay()
);
licenseInfo.addLicense(licenseDetail);
}
return licenseInfo;
}
public List<ComponentLicenseInfo> getComponentsWithRestrictiveLicenses(ProjectVersionWrapper projectWrapper)
throws IntegrationException {
LicenseReport report = generateLicenseReport(projectWrapper);
return report.getComponentsWithRestrictiveLicenses();
}
public Map<String, Integer> getLicenseDistribution(ProjectVersionWrapper projectWrapper)
throws IntegrationException {
LicenseReport report = generateLicenseReport(projectWrapper);
return report.getLicenseDistribution();
}
public boolean hasLicenseViolations(ProjectVersionWrapper projectWrapper)
throws IntegrationException {
LicenseReport report = generateLicenseReport(projectWrapper);
return report.hasRestrictiveLicenses();
}
}
class LicenseReport {
private final ProjectVersionWrapper projectWrapper;
private final List<ComponentLicenseInfo> componentLicenses;
private final Map<String, Integer> licenseDistribution;
private final Date reportDate;
public LicenseReport(ProjectVersionWrapper projectWrapper) {
this.projectWrapper = projectWrapper;
this.componentLicenses = new ArrayList<>();
this.licenseDistribution = new HashMap<>();
this.reportDate = new Date();
}
public void addComponentLicense(ComponentLicenseInfo licenseInfo) {
componentLicenses.add(licenseInfo);
}
public void calculateSummary() {
licenseDistribution.clear();
for (ComponentLicenseInfo componentLicense : componentLicenses) {
for (LicenseDetail license : componentLicense.getLicenses()) {
String licenseName = license.getName();
licenseDistribution.put(licenseName,
licenseDistribution.getOrDefault(licenseName, 0) + 1);
}
}
}
public List<ComponentLicenseInfo> getComponentsWithRestrictiveLicenses() {
List<ComponentLicenseInfo> restrictiveComponents = new ArrayList<>();
for (ComponentLicenseInfo component : componentLicenses) {
if (component.hasRestrictiveLicense()) {
restrictiveComponents.add(component);
}
}
return restrictiveComponents;
}
public boolean hasRestrictiveLicenses() {
return !getComponentsWithRestrictiveLicenses().isEmpty();
}
// Getters
public ProjectVersionWrapper getProjectWrapper() { return projectWrapper; }
public List<ComponentLicenseInfo> getComponentLicenses() { return componentLicenses; }
public Map<String, Integer> getLicenseDistribution() { return licenseDistribution; }
public Date getReportDate() { return reportDate; }
public int getTotalComponents() { return componentLicenses.size(); }
public String getProjectName() {
return projectWrapper.getProjectView().getName();
}
public String getProjectVersion() {
return projectWrapper.getProjectVersionView().getVersionName();
}
}
class ComponentLicenseInfo {
private final String componentName;
private final String componentVersion;
private final String componentUrl;
private final List<LicenseDetail> licenses;
public ComponentLicenseInfo(String componentName, String componentVersion, String componentUrl) {
this.componentName = componentName;
this.componentVersion = componentVersion;
this.componentUrl = componentUrl;
this.licenses = new ArrayList<>();
}
public void addLicense(LicenseDetail license) {
licenses.add(license);
}
public boolean hasLicenses() {
return !licenses.isEmpty();
}
public boolean hasRestrictiveLicense() {
// Define what constitutes a restrictive license
return licenses.stream()
.anyMatch(license -> isRestrictiveLicense(license.getName()));
}
private boolean isRestrictiveLicense(String licenseName) {
String lowerLicense = licenseName.toLowerCase();
return lowerLicense.contains("gpl") ||
lowerLicense.contains("agpl") ||
lowerLicense.contains("lgpl") ||
lowerLicense.contains("copyleft") ||
lowerLicense.contains("proprietary");
}
// Getters
public String getComponentName() { return componentName; }
public String getComponentVersion() { return componentVersion; }
public String getComponentUrl() { return componentUrl; }
public List<LicenseDetail> getLicenses() { return licenses; }
}
class LicenseDetail {
private final String name;
private final String licenseId;
private final String licenseType;
private final String licenseFamily;
private final String licenseDisplay;
public LicenseDetail(String name, String licenseId, String licenseType,
String licenseFamily, String licenseDisplay) {
this.name = name;
this.licenseId = licenseId;
this.licenseType = licenseType;
this.licenseFamily = licenseFamily;
this.licenseDisplay = licenseDisplay;
}
// Getters
public String getName() { return name; }
public String getLicenseId() { return licenseId; }
public String getLicenseType() { return licenseType; }
public String getLicenseFamily() { return licenseFamily; }
public String getLicenseDisplay() { return licenseDisplay; }
public boolean isOpenSource() {
return "OPEN_SOURCE".equalsIgnoreCase(licenseType);
}
public boolean isProprietary() {
return "PROPRIETARY".equalsIgnoreCase(licenseType);
}
}
Policy Management
package com.example.blackduck.policy;
import com.synopsys.integration.blackduck.api.generated.view.PolicyRuleView;
import com.synopsys.integration.blackduck.api.generated.view.PolicyView;
import com.synopsys.integration.blackduck.service.BlackDuckApiClient;
import com.synopsys.integration.blackduck.service.dataservice.PolicyService;
import com.synopsys.integration.blackduck.service.model.PolicyRuleExpressionSetBuilder;
import com.synopsys.integration.blackduck.service.model.ProjectVersionWrapper;
import com.synopsys.integration.exception.IntegrationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Optional;
public class PolicyManager {
private static final Logger logger = LoggerFactory.getLogger(PolicyManager.class);
private final PolicyService policyService;
private final BlackDuckApiClient blackDuckApiClient;
public PolicyManager(PolicyService policyService, BlackDuckApiClient blackDuckApiClient) {
this.policyService = policyService;
this.blackDuckApiClient = blackDuckApiClient;
}
public List<PolicyView> getAllPolicies() throws IntegrationException {
logger.debug("Retrieving all policies");
return policyService.getAllPolicies();
}
public Optional<PolicyView> getPolicyByName(String policyName) throws IntegrationException {
logger.debug("Looking up policy: {}", policyName);
List<PolicyView> allPolicies = getAllPolicies();
return allPolicies.stream()
.filter(policy -> policyName.equals(policy.getName()))
.findFirst();
}
public PolicyView createPolicy(String policyName, String description) throws IntegrationException {
logger.info("Creating policy: {}", policyName);
PolicyView policy = new PolicyView();
policy.setName(policyName);
policy.setDescription(description);
PolicyView createdPolicy = policyService.createPolicy(policy);
logger.info("Successfully created policy: {}", policyName);
return createdPolicy;
}
public PolicyRuleView createSecurityPolicyRule(String policyName, int maxCriticalVulns,
int maxHighVulns) throws IntegrationException {
logger.info("Creating security policy rule for policy: {}", policyName);
Optional<PolicyView> policy = getPolicyByName(policyName);
if (!policy.isPresent()) {
throw new IllegalArgumentException("Policy not found: " + policyName);
}
PolicyRuleExpressionSetBuilder expressionBuilder = new PolicyRuleExpressionSetBuilder();
// Add vulnerability severity conditions
if (maxCriticalVulns >= 0) {
expressionBuilder.addVulnerabilitySeverityCondition("CRITICAL", maxCriticalVulns);
}
if (maxHighVulns >= 0) {
expressionBuilder.addVulnerabilitySeverityCondition("HIGH", maxHighVulns);
}
PolicyRuleView policyRule = new PolicyRuleView();
policyRule.setName("Security Vulnerability Rule");
policyRule.setEnabled(true);
policyRule.setOverridable(true);
policyRule.setExpression(expressionBuilder.build());
PolicyRuleView createdRule = policyService.createPolicyRule(policy.get(), policyRule);
logger.info("Successfully created security policy rule");
return createdRule;
}
public PolicyRuleView createLicensePolicyRule(String policyName, List<String> bannedLicenses)
throws IntegrationException {
logger.info("Creating license policy rule for policy: {}", policyName);
Optional<PolicyView> policy = getPolicyByName(policyName);
if (!policy.isPresent()) {
throw new IllegalArgumentException("Policy not found: " + policyName);
}
PolicyRuleExpressionSetBuilder expressionBuilder = new PolicyRuleExpressionSetBuilder();
// Add license conditions
for (String license : bannedLicenses) {
expressionBuilder.addLicenseCondition(license);
}
PolicyRuleView policyRule = new PolicyRuleView();
policyRule.setName("License Compliance Rule");
policyRule.setEnabled(true);
policyRule.setOverridable(true);
policyRule.setExpression(expressionBuilder.build());
PolicyRuleView createdRule = policyService.createPolicyRule(policy.get(), policyRule);
logger.info("Successfully created license policy rule");
return createdRule;
}
public PolicyStatus checkPolicyCompliance(ProjectVersionWrapper projectWrapper)
throws IntegrationException {
logger.info("Checking policy compliance for project: {}",
projectWrapper.getProjectView().getName());
return policyService.getPolicyStatusForProjectVersion(projectWrapper.getProjectVersionView());
}
public boolean isPolicyCompliant(ProjectVersionWrapper projectWrapper) throws IntegrationException {
PolicyStatus policyStatus = checkPolicyCompliance(projectWrapper);
return policyStatus.getOverallStatus().isPassed();
}
public List<PolicyRuleView> getViolatedPolicies(ProjectVersionWrapper projectWrapper)
throws IntegrationException {
PolicyStatus policyStatus = checkPolicyCompliance(projectWrapper);
return policyStatus.getViolatedPolicies();
}
}
Automated Scanners and Integrations
Maven Project Scanner
package com.example.blackduck.scan;
import com.example.blackduck.BlackDuckClient;
import com.example.blackduck.projects.ProjectManager;
import com.synopsys.integration.blackduck.service.dataservice.ProjectBomService;
import com.synopsys.integration.blackduck.service.dataservice.ProjectService;
import com.synopsys.integration.blackduck.service.model.ProjectVersionWrapper;
import com.synopsys.integration.exception.IntegrationException;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
public class MavenProjectScanner {
private static final Logger logger = LoggerFactory.getLogger(MavenProjectScanner.class);
private final ProjectManager projectManager;
private final ComponentScanner componentScanner;
public MavenProjectScanner(ProjectManager projectManager, ComponentScanner componentScanner) {
this.projectManager = projectManager;
this.componentScanner = componentScanner;
}
public ScanResult scanMavenProject(Path pomFilePath, String projectName, String version)
throws IntegrationException, IOException, XmlPullParserException {
logger.info("Scanning Maven project: {} from {}", projectName, pomFilePath);
// Parse Maven POM
MavenProject mavenProject = parseMavenPom(pomFilePath);
// Create or get Black Duck project
ProjectVersionWrapper projectWrapper = projectManager.getOrCreateProject(
projectName, version, mavenProject.getDescription());
// Add dependencies to Black Duck project
int addedComponents = addDependenciesToProject(projectWrapper, mavenProject.getDependencies());
// Generate reports
VulnerabilityReport vulnReport = componentScanner.generateVulnerabilityReport(projectWrapper);
return new ScanResult(projectWrapper, mavenProject, vulnReport, addedComponents);
}
private MavenProject parseMavenPom(Path pomFilePath) throws IOException, XmlPullParserException {
MavenXpp3Reader reader = new MavenXpp3Reader();
Model model = reader.read(new FileReader(pomFilePath.toFile()));
MavenProject mavenProject = new MavenProject();
mavenProject.setGroupId(model.getGroupId());
mavenProject.setArtifactId(model.getArtifactId());
mavenProject.setVersion(model.getVersion());
mavenProject.setDescription(model.getDescription());
mavenProject.setDependencies(model.getDependencies());
logger.debug("Parsed Maven project: {}:{}:{}",
mavenProject.getGroupId(), mavenProject.getArtifactId(), mavenProject.getVersion());
return mavenProject;
}
private int addDependenciesToProject(ProjectVersionWrapper projectWrapper,
List<Dependency> dependencies) throws IntegrationException {
int addedCount = 0;
for (Dependency dependency : dependencies) {
try {
String componentName = dependency.getGroupId() + ":" + dependency.getArtifactId();
String componentVersion = dependency.getVersion();
// Clean version string (remove Maven properties, etc.)
componentVersion = cleanVersion(componentVersion);
if (componentVersion != null && !componentVersion.isEmpty()) {
componentScanner.addComponentToProject(projectWrapper, componentName,
componentVersion, "DYNAMICALLY_LINKED");
addedCount++;
logger.debug("Added component: {}:{}", componentName, componentVersion);
}
} catch (Exception e) {
logger.warn("Failed to add dependency: {}:{}",
dependency.getGroupId(), dependency.getArtifactId(), e);
}
}
logger.info("Added {} dependencies to project", addedCount);
return addedCount;
}
private String cleanVersion(String version) {
if (version == null) return null;
// Remove Maven properties like ${project.version}
if (version.startsWith("${") && version.endsWith("}")) {
return null;
}
// Remove version ranges
if (version.contains("[") || version.contains("(")) {
return version.replaceAll("[[\\]()]", "").split(",")[0].trim();
}
return version.trim();
}
public void scanMavenMultiModule(Path rootPomPath, String baseProjectName)
throws IntegrationException, IOException, XmlPullParserException {
File rootDir = rootPomPath.getParent().toFile();
// Find all POM files
List<File> pomFiles = findPomFiles(rootDir);
for (File pomFile : pomFiles) {
try {
MavenProject mavenProject = parseMavenPom(pomFile.toPath());
String projectName = baseProjectName + " - " + mavenProject.getArtifactId();
scanMavenProject(pomFile.toPath(), projectName, mavenProject.getVersion());
} catch (Exception e) {
logger.error("Failed to scan module: {}", pomFile.getName(), e);
}
}
}
private List<File> findPomFiles(File directory) {
List<File> pomFiles = new java.util.ArrayList<>();
findPomFilesRecursive(directory, pomFiles);
return pomFiles;
}
private void findPomFilesRecursive(File directory, List<File> pomFiles) {
File[] files = directory.listFiles();
if (files == null) return;
for (File file : files) {
if (file.isDirectory()) {
findPomFilesRecursive(file, pomFiles);
} else if (file.getName().equals("pom.xml")) {
pomFiles.add(file);
}
}
}
}
class MavenProject {
private String groupId;
private String artifactId;
private String version;
private String description;
private List<Dependency> dependencies;
// Getters and setters
public String getGroupId() { return groupId; }
public void setGroupId(String groupId) { this.groupId = groupId; }
public String getArtifactId() { return artifactId; }
public void setArtifactId(String artifactId) { this.artifactId = artifactId; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public List<Dependency> getDependencies() { return dependencies; }
public void setDependencies(List<Dependency> dependencies) { this.dependencies = dependencies; }
}
class ScanResult {
private final ProjectVersionWrapper projectWrapper;
private final MavenProject mavenProject;
private final VulnerabilityReport vulnerabilityReport;
private final int componentsAdded;
public ScanResult(ProjectVersionWrapper projectWrapper, MavenProject mavenProject,
VulnerabilityReport vulnerabilityReport, int componentsAdded) {
this.projectWrapper = projectWrapper;
this.mavenProject = mavenProject;
this.vulnerabilityReport = vulnerabilityReport;
this.componentsAdded = componentsAdded;
}
// Getters
public ProjectVersionWrapper getProjectWrapper() { return projectWrapper; }
public MavenProject getMavenProject() { return mavenProject; }
public VulnerabilityReport getVulnerabilityReport() { return vulnerabilityReport; }
public int getComponentsAdded() { return componentsAdded; }
public boolean hasVulnerabilities() {
return vulnerabilityReport.getTotalVulnerabilities() > 0;
}
public boolean hasCriticalVulnerabilities() {
return !vulnerabilityReport.getCriticalComponents().isEmpty();
}
}
Report Generation
package com.example.blackduck.report;
import com.example.blackduck.license.LicenseReport;
import com.example.blackduck.scan.VulnerabilityReport;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
public class ReportGenerator {
private static final Logger logger = LoggerFactory.getLogger(ReportGenerator.class);
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
private final ObjectMapper objectMapper;
public ReportGenerator() {
this.objectMapper = new ObjectMapper();
this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
}
public void generateHtmlReport(VulnerabilityReport vulnReport, LicenseReport licenseReport,
Path outputDir) throws IOException {
String timestamp = DATE_FORMAT.format(new Date());
String filename = "blackduck_report_" + timestamp + ".html";
Path outputPath = outputDir.resolve(filename);
String htmlContent = generateHtmlContent(vulnReport, licenseReport);
try (FileWriter writer = new FileWriter(outputPath.toFile())) {
writer.write(htmlContent);
}
logger.info("HTML report generated: {}", outputPath);
}
public void generateJsonReport(VulnerabilityReport vulnReport, LicenseReport licenseReport,
Path outputDir) throws IOException {
String timestamp = DATE_FORMAT.format(new Date());
String filename = "blackduck_report_" + timestamp + ".json";
Path outputPath = outputDir.resolve(filename);
CombinedReport combinedReport = new CombinedReport(vulnReport, licenseReport);
objectMapper.writeValue(outputPath.toFile(), combinedReport);
logger.info("JSON report generated: {}", outputPath);
}
public void generateConsoleReport(VulnerabilityReport vulnReport, LicenseReport licenseReport) {
System.out.println("=== BLACK DUCK SECURITY REPORT ===");
System.out.printf("Project: %s%n", vulnReport.getProjectName());
System.out.printf("Version: %s%n", vulnReport.getProjectVersion());
System.out.printf("Report Date: %s%n", vulnReport.getReportDate());
System.out.println();
// Vulnerability Summary
System.out.println("VULNERABILITY SUMMARY:");
Map<String, Integer> vulnSummary = vulnReport.getVulnerabilitySummary();
for (Map.Entry<String, Integer> entry : vulnSummary.entrySet()) {
System.out.printf(" %s: %d%n", entry.getKey(), entry.getValue());
}
System.out.printf("Total Vulnerabilities: %d%n", vulnReport.getTotalVulnerabilities());
System.out.println();
// Critical Components
if (!vulnReport.getCriticalComponents().isEmpty()) {
System.out.println("CRITICAL COMPONENTS:");
vulnReport.getCriticalComponents().forEach(component -> {
System.out.printf(" %s:%s - %d vulnerabilities%n",
component.getComponentName(), component.getComponentVersion(),
component.getVulnerabilityCount());
});
System.out.println();
}
// License Summary
System.out.println("LICENSE SUMMARY:");
Map<String, Integer> licenseDistribution = licenseReport.getLicenseDistribution();
for (Map.Entry<String, Integer> entry : licenseDistribution.entrySet()) {
System.out.printf(" %s: %d%n", entry.getKey(), entry.getValue());
}
System.out.printf("Total Components: %d%n", licenseReport.getTotalComponents());
if (licenseReport.hasRestrictiveLicenses()) {
System.out.println("WARNING: Restrictive licenses detected!");
}
}
private String generateHtmlContent(VulnerabilityReport vulnReport, LicenseReport licenseReport) {
return """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Black Duck Security Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background: #2c3e50; color: white; padding: 20px; border-radius: 5px; }
.summary { margin: 20px 0; }
.section { margin: 20px 0; border: 1px solid #ddd; padding: 15px; border-radius: 5px; }
.critical { color: #e74c3c; font-weight: bold; }
.high { color: #e67e22; }
.medium { color: #f39c12; }
.low { color: #27ae60; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<div class="header">
<h1>Black Duck Security Report</h1>
<p>Project: """ + vulnReport.getProjectName() + """</p>
<p>Version: """ + vulnReport.getProjectVersion() + """</p>
<p>Generated: """ + vulnReport.getReportDate() + """</p>
</div>
<div class="summary">
<h2>Executive Summary</h2>
<p>Total Vulnerabilities: """ + vulnReport.getTotalVulnerabilities() + """</p>
<p>Components with Critical Vulnerabilities: """ + vulnReport.getCriticalComponents().size() + """</p>
<p>Restrictive Licenses: """ + (licenseReport.hasRestrictiveLicenses() ? "Yes" : "No") + """</p>
</div>
<div class="section">
<h2>Vulnerability Summary</h2>
<table>
<tr><th>Severity</th><th>Count</th></tr>
""" + generateVulnerabilitySummaryRows(vulnReport) + """
</table>
</div>
<div class="section">
<h2>License Distribution</h2>
<table>
<tr><th>License</th><th>Count</th></tr>
""" + generateLicenseDistributionRows(licenseReport) + """
</table>
</div>
<div class="section">
<h2>Critical Components</h2>
""" + generateCriticalComponentsList(vulnReport) + """
</div>
</body>
</html>
""";
}
private String generateVulnerabilitySummaryRows(VulnerabilityReport vulnReport) {
StringBuilder rows = new StringBuilder();
Map<String, Integer> summary = vulnReport.getVulnerabilitySummary();
for (Map.Entry<String, Integer> entry : summary.entrySet()) {
String severityClass = getSeverityClass(entry.getKey());
rows.append(String.format("<tr><td class='%s'>%s</td><td>%d</td></tr>",
severityClass, entry.getKey(), entry.getValue()));
}
return rows.toString();
}
private String generateLicenseDistributionRows(LicenseReport licenseReport) {
StringBuilder rows = new StringBuilder();
Map<String, Integer> distribution = licenseReport.getLicenseDistribution();
for (Map.Entry<String, Integer> entry : distribution.entrySet()) {
rows.append(String.format("<tr><td>%s</td><td>%d</td></tr>",
entry.getKey(), entry.getValue()));
}
return rows.toString();
}
private String generateCriticalComponentsList(VulnerabilityReport vulnReport) {
if (vulnReport.getCriticalComponents().isEmpty()) {
return "<p>No critical components found.</p>";
}
StringBuilder list = new StringBuilder("<ul>");
for (var component : vulnReport.getCriticalComponents()) {
list.append(String.format("<li>%s:%s - %d vulnerabilities</li>",
component.getComponentName(), component.getComponentVersion(),
component.getVulnerabilityCount()));
}
list.append("</ul>");
return list.toString();
}
private String getSeverityClass(String severity) {
if (severity == null) return "low";
return switch (severity.toLowerCase()) {
case "critical" -> "critical";
case "high" -> "high";
case "medium" -> "medium";
default -> "low";
};
}
}
class CombinedReport {
private final VulnerabilityReport vulnerabilityReport;
private final LicenseReport licenseReport;
private final Date generationDate;
public CombinedReport(VulnerabilityReport vulnerabilityReport, LicenseReport licenseReport) {
this.vulnerabilityReport = vulnerabilityReport;
this.licenseReport = licenseReport;
this.generationDate = new Date();
}
// Getters for JSON serialization
public VulnerabilityReport getVulnerabilityReport() { return vulnerabilityReport; }
public LicenseReport getLicenseReport() { return licenseReport; }
public Date getGenerationDate() { return generationDate; }
}
Usage Example
package com.example.blackduck;
import com.example.blackduck.license.LicenseComplianceManager;
import com.example.blackduck.license.LicenseReport;
import com.example.blackduck.projects.ProjectManager;
import com.example.blackduck.report.ReportGenerator;
import com.example.blackduck.scan.*;
import com.synopsys.integration.blackduck.service.dataservice.*;
import com.synopsys.integration.exception.IntegrationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
public class BlackDuckExample {
private static final Logger logger = LoggerFactory.getLogger(BlackDuckExample.class);
public static void main(String[] args) {
// Configuration
String hubUrl = "https://your-blackduck-hub.com";
String apiToken = "your-api-token";
String projectName = "Example Project";
String projectVersion = "1.0.0";
Path pomFilePath = Paths.get("path/to/pom.xml");
Path reportOutputDir = Paths.get("reports");
BlackDuckClient blackDuckClient = null;
try {
// Initialize Black Duck client
blackDuckClient = new BlackDuckClient(hubUrl, apiToken, 120);
if (!blackDuckClient.testConnection()) {
logger.error("Failed to connect to Black Duck Hub");
System.exit(1);
}
// Get services
BlackDuckServicesFactory servicesFactory = blackDuckClient.getServicesFactory();
ProjectService projectService = servicesFactory.createProjectService();
ProjectBomService projectBomService = servicesFactory.createProjectBomService();
ComponentService componentService = servicesFactory.createComponentService();
LicenseService licenseService = servicesFactory.createLicenseService();
PolicyService policyService = servicesFactory.createPolicyService();
// Initialize managers
ProjectManager projectManager = new ProjectManager(projectService, blackDuckClient.getApiClient());
ComponentScanner componentScanner = new ComponentScanner(componentService, projectBomService,
blackDuckClient.getApiClient());
LicenseComplianceManager licenseManager = new LicenseComplianceManager(licenseService,
projectBomService, blackDuckClient.getApiClient());
ReportGenerator reportGenerator = new ReportGenerator();
// Scan Maven project
MavenProjectScanner mavenScanner = new MavenProjectScanner(projectManager, componentScanner);
ScanResult scanResult = mavenScanner.scanMavenProject(pomFilePath, projectName, projectVersion);
// Generate license report
LicenseReport licenseReport = licenseManager.generateLicenseReport(
scanResult.getProjectWrapper());
// Generate reports
reportGenerator.generateConsoleReport(scanResult.getVulnerabilityReport(), licenseReport);
reportGenerator.generateHtmlReport(scanResult.getVulnerabilityReport(), licenseReport, reportOutputDir);
reportGenerator.generateJsonReport(scanResult.getVulnerabilityReport(), licenseReport, reportOutputDir);
// Check for critical issues
if (scanResult.hasCriticalVulnerabilities()) {
logger.warn("CRITICAL: Project contains components with critical vulnerabilities");
System.exit(1);
}
if (licenseReport.hasRestrictiveLicenses()) {
logger.warn("WARNING: Project contains restrictive licenses");
}
logger.info("Black Duck analysis completed successfully");
} catch (Exception e) {
logger.error("Black Duck analysis failed", e);
System.exit(1);
} finally {
if (blackDuckClient != null) {
blackDuckClient.close();
}
}
}
}
GitHub Actions Integration
name: Black Duck Security Scan
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 0 * * 0' # Weekly on Sunday
jobs:
blackduck-scan:
name: "Black Duck Security Scan"
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build project
run: mvn -B compile -DskipTests
- name: Run Black Duck Scan
run: |
mvn -B exec:java -Dexec.mainClass="com.example.blackduck.BlackDuckExample" \
-Dexec.args="--pom pom.xml --output reports"
env:
BLACKDUCK_URL: ${{ secrets.BLACKDUCK_URL }}
BLACKDUCK_TOKEN: ${{ secrets.BLACKDUCK_TOKEN }}
- name: Upload Security Report
uses: actions/upload-artifact@v3
with:
name: blackduck-security-report
path: reports/
- name: Check for Critical Vulnerabilities
run: |
# This would check the exit code or report contents
# and fail the build if critical vulnerabilities are found
if grep -q "CRITICAL" reports/blackduck_report_*.json; then
echo "Critical vulnerabilities found - failing build"
exit 1
fi
Conclusion
This comprehensive Black Duck Hub implementation for Java provides:
- Complete integration with Black Duck Hub REST API
- Automated dependency scanning for Maven projects
- Vulnerability analysis with severity classification
- License compliance management with restrictive license detection
- Policy management for security and compliance enforcement
- Comprehensive reporting in HTML, JSON, and console formats
- CI/CD integration with GitHub Actions
Key benefits include:
- Automated Open Source Management - Continuous monitoring of dependencies
- Security Vulnerability Detection - Early identification of security risks
- License Compliance - Avoid legal issues with open-source licenses
- Policy Enforcement - Automated compliance checking
- Comprehensive Reporting - Actionable insights for development teams
The implementation follows industry best practices and provides a solid foundation for integrating Black Duck Hub into Java development workflows for comprehensive software composition analysis.
Secure Java Supply Chain, Minimal Containers & Runtime Security (Alpine, Distroless, Signing, SBOM & Kubernetes Controls)
https://macronepal.com/blog/alpine-linux-security-in-java-complete-guide/
Explains how Alpine Linux is used as a lightweight base for Java containers to reduce image size and attack surface, while discussing tradeoffs like musl compatibility, CVE handling, and additional hardening requirements for production security.
https://macronepal.com/blog/the-minimalists-approach-building-ultra-secure-java-applications-with-scratch-base-images/
Explains using scratch base images for Java applications to create extremely minimal containers with almost zero attack surface, where only the compiled Java application and runtime dependencies exist.
https://macronepal.com/blog/distroless-containers-in-java-minimal-secure-containers-for-jvm-applications/
Explains distroless Java containers that remove shells, package managers, and unnecessary OS tools, significantly reducing vulnerabilities while improving security posture for JVM workloads.
https://macronepal.com/blog/revolutionizing-container-security-implementing-chainguard-images-for-java-applications/
Explains Chainguard images for Java, which are secure-by-default, CVE-minimized container images with SBOMs and cryptographic signing, designed for modern supply-chain security.
https://macronepal.com/blog/seccomp-filtering-in-java-comprehensive-security-sandboxing/
Explains seccomp syscall filtering in Linux to restrict what system calls Java applications can make, reducing the impact of exploits by limiting kernel-level access.
https://macronepal.com/blog/in-toto-attestations-in-java/
Explains in-toto framework integration in Java to create cryptographically verifiable attestations across the software supply chain, ensuring every build step is trusted and auditable.
https://macronepal.com/blog/fulcio-integration-in-java-code-signing-certificate-infrastructure/
Explains Fulcio integration for Java, which issues short-lived certificates for code signing in a zero-trust supply chain, enabling secure identity-based signing of artifacts.
https://macronepal.com/blog/tekton-supply-chain-in-java-comprehensive-ci-cd-pipeline-implementation/
Explains using Tekton CI/CD pipelines for Java applications to automate secure builds, testing, signing, and deployment with supply-chain security controls built in.
https://macronepal.com/blog/slsa-provenance-in-java-complete-guide-to-supply-chain-security-2/
Explains SLSA (Supply-chain Levels for Software Artifacts) provenance in Java builds, ensuring traceability of how software is built, from source code to final container image.
https://macronepal.com/blog/notary-project-in-java-complete-implementation-guide/
Explains the Notary Project for Java container security, enabling cryptographic signing and verification of container images and artifacts to prevent tampering in deployment pipelines.