Container Security: Implementing Snyk Container Scanning in Java Applications

Snyk Container provides comprehensive security scanning for container images, identifying vulnerabilities in operating system packages, application dependencies, and configuration issues. This guide covers complete integration of Snyk Container scanning into Java applications and CI/CD pipelines.

Architecture Overview

Java Application → Snyk CLI / API → Container Registry → Vulnerability DB
↑
(Scan Results /
Security Reports)

Step 1: Dependencies Setup

Maven Dependencies

<!-- pom.xml -->
<dependencies>
<!-- Snyk REST API Client -->
<dependency>
<groupId>com.snyk</groupId>
<artifactId>snyk-api-client</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Docker Java Client -->
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java</artifactId>
<version>3.3.0</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- HTTP Client -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Process Execution -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>1.3</version>
</dependency>
</dependencies>

Step 2: Configuration Classes

Snyk Configuration

// src/main/java/com/company/snyk/config/SnykConfig.java
package com.company.snyk.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "snyk")
public class SnykConfig {
// API Configuration
private String apiToken;
private String apiUrl = "https://api.snyk.io/v1";
private String apiVersion = "2023-08-24";
// CLI Configuration
private String cliPath = "snyk";
private boolean cliAutoUpdate = true;
// Scan Configuration
private SeverityThreshold severityThreshold = SeverityThreshold.MEDIUM;
private boolean failOnIssues = true;
private int maxCriticalIssues = 0;
private int maxHighIssues = 10;
private int maxMediumIssues = 50;
// Reporting
private boolean generateSarif = true;
private boolean generateHtml = false;
private String outputDirectory = "./snyk-reports";
// Registry Configuration
private String registryUrl;
private String registryUsername;
private String registryPassword;
// Docker Configuration
private String dockerHost = "unix:///var/run/docker.sock";
private boolean useDockerDaemon = true;
public enum SeverityThreshold {
LOW, MEDIUM, HIGH, CRITICAL
}
}

Docker Configuration

// src/main/java/com/company/snyk/config/DockerConfig.java
package com.company.snyk.config;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.core.DockerClientBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@Slf4j
public class DockerConfig {
@Value("${snyk.docker.host:unix:///var/run/docker.sock}")
private String dockerHost;
@Bean
public DockerClient dockerClient() {
try {
DockerClient dockerClient = DockerClientBuilder.getInstance(dockerHost).build();
log.info("Docker client initialized successfully");
return dockerClient;
} catch (Exception e) {
log.warn("Failed to initialize Docker client: {}", e.getMessage());
return null;
}
}
}

Step 3: Snyk API Client Implementation

Snyk REST API Client

// src/main/java/com/company/snyk/client/SnykApiClient.java
package com.company.snyk.client;
import com.company.snyk.config.SnykConfig;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Component
@Slf4j
public class SnykApiClient {
private final SnykConfig snykConfig;
private final ObjectMapper objectMapper;
private final CloseableHttpClient httpClient;
public SnykApiClient(SnykConfig snykConfig) {
this.snykConfig = snykConfig;
this.objectMapper = new ObjectMapper();
this.httpClient = HttpClients.createDefault();
}
/**
* Test Snyk API connectivity
*/
public boolean testConnection() {
try {
HttpGet request = new HttpGet(snykConfig.getApiUrl() + "/user/me");
setAuthHeaders(request);
try (CloseableHttpResponse response = httpClient.execute(request)) {
return response.getCode() == 200;
}
} catch (Exception e) {
log.error("Snyk API connection test failed", e);
return false;
}
}
/**
* Scan a container image using Snyk API
*/
public ScanResult scanContainerImage(String imageName, String tag) {
try {
String endpoint = snykConfig.getApiUrl() + "/test-dependencies";
HttpPost request = new HttpPost(endpoint);
setAuthHeaders(request);
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("image", imageName + ":" + tag);
requestBody.put("org", getDefaultOrg());
String jsonBody = objectMapper.writeValueAsString(requestBody);
request.setEntity(new StringEntity(jsonBody, ContentType.APPLICATION_JSON));
try (CloseableHttpResponse response = httpClient.execute(request)) {
String responseBody = EntityUtils.toString(response.getEntity());
if (response.getCode() == 200) {
return parseScanResult(responseBody);
} else {
log.error("Snyk API scan failed: {}", responseBody);
throw new RuntimeException("Snyk API scan failed: " + response.getCode());
}
}
} catch (Exception e) {
log.error("Container image scan failed", e);
throw new RuntimeException("Container scan failed", e);
}
}
/**
* Monitor container image for ongoing security
*/
public MonitorResult monitorContainerImage(String imageName, String tag, String projectName) {
try {
String endpoint = snykConfig.getApiUrl() + "/monitor-dependencies";
HttpPost request = new HttpPost(endpoint);
setAuthHeaders(request);
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("image", imageName + ":" + tag);
requestBody.put("org", getDefaultOrg());
requestBody.put("projectName", projectName);
String jsonBody = objectMapper.writeValueAsString(requestBody);
request.setEntity(new StringEntity(jsonBody, ContentType.APPLICATION_JSON));
try (CloseableHttpResponse response = httpClient.execute(request)) {
String responseBody = EntityUtils.toString(response.getEntity());
return objectMapper.readValue(responseBody, MonitorResult.class);
}
} catch (Exception e) {
log.error("Container image monitoring failed", e);
throw new RuntimeException("Container monitoring failed", e);
}
}
/**
* Get vulnerability report for a container image
*/
public VulnerabilityReport getVulnerabilityReport(String imageId) {
try {
String endpoint = String.format("%s/reports/%s", snykConfig.getApiUrl(), imageId);
HttpGet request = new HttpGet(endpoint);
setAuthHeaders(request);
try (CloseableHttpResponse response = httpClient.execute(request)) {
String responseBody = EntityUtils.toString(response.getEntity());
return objectMapper.readValue(responseBody, VulnerabilityReport.class);
}
} catch (Exception e) {
log.error("Failed to get vulnerability report", e);
throw new RuntimeException("Vulnerability report fetch failed", e);
}
}
/**
* Upload Dockerfile for analysis
*/
public ScanResult analyzeDockerfile(File dockerfile, String imageName) {
try {
String endpoint = snykConfig.getApiUrl() + "/test-dockerfile";
HttpPost request = new HttpPost(endpoint);
setAuthHeaders(request);
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.addBinaryBody("dockerfile", dockerfile, ContentType.DEFAULT_BINARY, "Dockerfile");
builder.addTextBody("image", imageName, ContentType.TEXT_PLAIN);
builder.addTextBody("org", getDefaultOrg(), ContentType.TEXT_PLAIN);
request.setEntity(builder.build());
try (CloseableHttpResponse response = httpClient.execute(request)) {
String responseBody = EntityUtils.toString(response.getEntity());
return parseScanResult(responseBody);
}
} catch (Exception e) {
log.error("Dockerfile analysis failed", e);
throw new RuntimeException("Dockerfile analysis failed", e);
}
}
private ScanResult parseScanResult(String responseBody) throws IOException {
JsonNode root = objectMapper.readTree(responseBody);
ScanResult result = new ScanResult();
result.setOk(root.path("ok").asBoolean());
result.setIssueCount(root.path("issues").path("vulnerabilities").size());
result.setDependencyCount(root.path("dependencyCount").asInt());
// Parse vulnerabilities
JsonNode vulnerabilities = root.path("issues").path("vulnerabilities");
for (JsonNode vuln : vulnerabilities) {
Vulnerability vulnerability = new Vulnerability();
vulnerability.setId(vuln.path("id").asText());
vulnerability.setTitle(vuln.path("title").asText());
vulnerability.setSeverity(vuln.path("severity").asText());
vulnerability.setPackageName(vuln.path("packageName").asText());
vulnerability.setVersion(vuln.path("version").asText());
vulnerability.setFixedIn(vuln.path("fixedIn").asText());
result.addVulnerability(vulnerability);
}
return result;
}
private void setAuthHeaders(org.apache.hc.client5.http.classic.methods.HttpUriRequest request) {
request.setHeader("Authorization", "token " + snykConfig.getApiToken());
request.setHeader("Content-Type", "application/json");
request.setHeader("User-Agent", "snyk-java-client/1.0.0");
}
private String getDefaultOrg() {
// Extract org from API token or use default
return "default-org";
}
@Data
public static class ScanResult {
private boolean ok;
private int issueCount;
private int dependencyCount;
private java.util.List<Vulnerability> vulnerabilities = new java.util.ArrayList<>();
private String summary;
private String scanDate;
public void addVulnerability(Vulnerability vulnerability) {
this.vulnerabilities.add(vulnerability);
}
public long getCriticalCount() {
return vulnerabilities.stream().filter(v -> "critical".equals(v.getSeverity())).count();
}
public long getHighCount() {
return vulnerabilities.stream().filter(v -> "high".equals(v.getSeverity())).count();
}
public long getMediumCount() {
return vulnerabilities.stream().filter(v -> "medium".equals(v.getSeverity())).count();
}
public long getLowCount() {
return vulnerabilities.stream().filter(v -> "low".equals(v.getSeverity())).count();
}
}
@Data
public static class Vulnerability {
private String id;
private String title;
private String severity;
private String packageName;
private String version;
private String fixedIn;
private String description;
private String exploitMaturity;
private java.util.List<String> identifiers = new java.util.ArrayList<>();
private java.util.List<String> references = new java.util.ArrayList<>();
}
@Data
public static class MonitorResult {
private String id;
private String uri;
private boolean ok;
private String projectName;
private String created;
}
@Data
public static class VulnerabilityReport {
private String id;
private String status;
private ScanResult results;
private String generatedAt;
}
}

Step 4: Snyk CLI Wrapper

CLI Execution Service

// src/main/java/com/company/snyk/service/SnykCliService.java
package com.company.snyk.service;
import com.company.snyk.config.SnykConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.PumpStreamHandler;
import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
@Service
@Slf4j
public class SnykCliService {
private final SnykConfig snykConfig;
public SnykCliService(SnykConfig snykConfig) {
this.snykConfig = snykConfig;
}
/**
* Execute Snyk CLI command
*/
public CliResult executeCommand(List<String> args, File workingDirectory) {
try {
List<String> command = new ArrayList<>();
command.add(snykConfig.getCliPath());
command.addAll(args);
CommandLine commandLine = CommandLine.parse(String.join(" ", command));
DefaultExecutor executor = new DefaultExecutor();
if (workingDirectory != null) {
executor.setWorkingDirectory(workingDirectory);
}
// Set timeout (10 minutes)
ExecuteWatchdog watchdog = new ExecuteWatchdog(600000);
executor.setWatchdog(watchdog);
// Capture output
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream, errorStream);
executor.setStreamHandler(streamHandler);
int exitValue = executor.execute(commandLine);
String output = outputStream.toString();
String error = errorStream.toString();
return new CliResult(exitValue, output, error);
} catch (IOException e) {
log.error("Snyk CLI execution failed", e);
return new CliResult(-1, "", "Execution failed: " + e.getMessage());
}
}
/**
* Scan container image using Snyk CLI
*/
public ScanResult scanContainerImage(String imageName, String tag) {
List<String> args = new ArrayList<>();
args.add("container");
args.add("test");
args.add(imageName + ":" + tag);
args.add("--json");
args.add("--severity-threshold=" + snykConfig.getSeverityThreshold().name().toLowerCase());
if (snykConfig.isGenerateSarif()) {
Path outputDir = Paths.get(snykConfig.getOutputDirectory());
ensureDirectoryExists(outputDir);
String sarifFile = outputDir.resolve("snyk-container-scan.sarif").toString();
args.add("--sarif");
args.add("--sarif-file-output=" + sarifFile);
}
CliResult result = executeCommand(args, null);
if (result.getExitCode() == 0) {
log.info("Container scan completed successfully");
return parseScanResult(result.getOutput());
} else {
log.error("Container scan failed: {}", result.getError());
throw new RuntimeException("Container scan failed: " + result.getError());
}
}
/**
* Monitor container image
*/
public MonitorResult monitorContainerImage(String imageName, String tag, String projectName) {
List<String> args = new ArrayList<>();
args.add("container");
args.add("monitor");
args.add(imageName + ":" + tag);
args.add("--project-name=" + projectName);
args.add("--json");
CliResult result = executeCommand(args, null);
if (result.getExitCode() == 0) {
log.info("Container monitoring setup successfully");
return parseMonitorResult(result.getOutput());
} else {
log.error("Container monitoring failed: {}", result.getError());
throw new RuntimeException("Container monitoring failed: " + result.getError());
}
}
/**
* Test Dockerfile
*/
public ScanResult testDockerfile(File dockerfile, String baseImage) {
List<String> args = new ArrayList<>();
args.add("container");
args.add("test");
baseImage != null ? baseImage : "--file=" + dockerfile.getAbsolutePath();
args.add("--json");
args.add("--severity-threshold=" + snykConfig.getSeverityThreshold().name().toLowerCase());
if (baseImage != null) {
args.add("--file=" + dockerfile.getAbsolutePath());
}
CliResult result = executeCommand(args, dockerfile.getParentFile());
if (result.getExitCode() == 0) {
log.info("Dockerfile test completed successfully");
return parseScanResult(result.getOutput());
} else {
log.error("Dockerfile test failed: {}", result.getError());
throw new RuntimeException("Dockerfile test failed: " + result.getError());
}
}
/**
* Generate SBOM for container image
*/
public String generateSbom(String imageName, String tag, String format) {
List<String> args = new ArrayList<>();
args.add("container");
args.add("sbom");
args.add(imageName + ":" + tag);
args.add("--format=" + format);
CliResult result = executeCommand(args, null);
if (result.getExitCode() == 0) {
log.info("SBOM generated successfully in {} format", format);
return result.getOutput();
} else {
log.error("SBOM generation failed: {}", result.getError());
throw new RuntimeException("SBOM generation failed: " + result.getError());
}
}
/**
* Check if Snyk CLI is available
*/
public boolean isCliAvailable() {
List<String> args = List.of("--version");
CliResult result = executeCommand(args, null);
return result.getExitCode() == 0;
}
/**
* Authenticate Snyk CLI
*/
public boolean authenticate() {
List<String> args = List.of("auth", snykConfig.getApiToken());
CliResult result = executeCommand(args, null);
return result.getExitCode() == 0;
}
private ScanResult parseScanResult(String jsonOutput) {
try {
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
return mapper.readValue(jsonOutput, ScanResult.class);
} catch (Exception e) {
log.error("Failed to parse scan result", e);
throw new RuntimeException("Failed to parse scan result", e);
}
}
private MonitorResult parseMonitorResult(String jsonOutput) {
try {
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
return mapper.readValue(jsonOutput, MonitorResult.class);
} catch (Exception e) {
log.error("Failed to parse monitor result", e);
throw new RuntimeException("Failed to parse monitor result", e);
}
}
private void ensureDirectoryExists(Path directory) {
try {
Files.createDirectories(directory);
} catch (IOException e) {
log.error("Failed to create output directory: {}", directory, e);
}
}
@Data
public static class CliResult {
private final int exitCode;
private final String output;
private final String error;
public boolean isSuccess() {
return exitCode == 0;
}
}
}

Step 5: Container Scan Service

Comprehensive Scan Service

// src/main/java/com/company/snyk/service/ContainerScanService.java
package com.company.snyk.service;
import com.company.snyk.client.SnykApiClient;
import com.company.snyk.config.SnykConfig;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.model.Image;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Slf4j
public class ContainerScanService {
private final SnykApiClient snykApiClient;
private final SnykCliService snykCliService;
private final SnykConfig snykConfig;
private final DockerClient dockerClient;
public ContainerScanService(SnykApiClient snykApiClient, 
SnykCliService snykCliService, 
SnykConfig snykConfig,
DockerClient dockerClient) {
this.snykApiClient = snykApiClient;
this.snykCliService = snykCliService;
this.snykConfig = snykConfig;
this.dockerClient = dockerClient;
}
/**
* Comprehensive container image scan
*/
public ComprehensiveScanResult scanImageComprehensive(String imageName, String tag) {
log.info("Starting comprehensive scan for image: {}:{}", imageName, tag);
ComprehensiveScanResult result = new ComprehensiveScanResult();
result.setImageName(imageName);
result.setTag(tag);
result.setScanDate(LocalDateTime.now());
try {
// Scan using CLI
SnykCliService.ScanResult cliResult = snykCliService.scanContainerImage(imageName, tag);
result.setCliScanResult(cliResult);
// Scan using API
SnykApiClient.ScanResult apiResult = snykApiClient.scanContainerImage(imageName, tag);
result.setApiScanResult(apiResult);
// Generate SBOM
String sbom = snykCliService.generateSbom(imageName, tag, "cyclonedx1.4+json");
result.setSbom(sbom);
// Analyze image metadata
ImageAnalysis imageAnalysis = analyzeImageMetadata(imageName, tag);
result.setImageAnalysis(imageAnalysis);
// Evaluate security posture
SecurityPosture posture = evaluateSecurityPosture(cliResult, apiResult, imageAnalysis);
result.setSecurityPosture(posture);
// Generate report
String report = generateScanReport(result);
result.setReport(report);
log.info("Comprehensive scan completed for image: {}:{}", imageName, tag);
} catch (Exception e) {
log.error("Comprehensive scan failed for image: {}:{}", imageName, tag, e);
result.setError(e.getMessage());
result.setSuccess(false);
}
return result;
}
/**
* Scan multiple images in batch
*/
public BatchScanResult scanImagesBatch(List<String> images) {
BatchScanResult batchResult = new BatchScanResult();
batchResult.setTotalImages(images.size());
List<ComprehensiveScanResult> results = new ArrayList<>();
List<String> failedImages = new ArrayList<>();
for (String imageWithTag : images) {
try {
String[] parts = imageWithTag.split(":");
String imageName = parts[0];
String tag = parts.length > 1 ? parts[1] : "latest";
ComprehensiveScanResult result = scanImageComprehensive(imageName, tag);
results.add(result);
if (!result.isSuccess()) {
failedImages.add(imageWithTag);
}
} catch (Exception e) {
log.error("Batch scan failed for image: {}", imageWithTag, e);
failedImages.add(imageWithTag);
}
}
batchResult.setScanResults(results);
batchResult.setFailedImages(failedImages);
batchResult.setSuccessCount(results.size() - failedImages.size());
batchResult.setFailureCount(failedImages.size());
return batchResult;
}
/**
* Scan all local Docker images
*/
public BatchScanResult scanAllLocalImages() {
try {
List<Image> images = dockerClient.listImagesCmd().exec();
List<String> imageTags = images.stream()
.map(this::extractImageTags)
.flatMap(List::stream)
.collect(Collectors.toList());
log.info("Found {} local images to scan", imageTags.size());
return scanImagesBatch(imageTags);
} catch (Exception e) {
log.error("Failed to scan local images", e);
throw new RuntimeException("Local image scan failed", e);
}
}
/**
* Scan Dockerfile and base image
*/
public DockerfileScanResult scanDockerfile(File dockerfile, String baseImage) {
DockerfileScanResult result = new DockerfileScanResult();
result.setDockerfilePath(dockerfile.getAbsolutePath());
result.setScanDate(LocalDateTime.now());
try {
// Test Dockerfile
SnykCliService.ScanResult dockerfileResult = snykCliService.testDockerfile(dockerfile, baseImage);
result.setDockerfileScanResult(dockerfileResult);
// If base image provided, scan it too
if (baseImage != null) {
SnykCliService.ScanResult baseImageResult = snykCliService.scanContainerImage(baseImage, "latest");
result.setBaseImageScanResult(baseImageResult);
}
// Analyze Dockerfile best practices
DockerfileAnalysis analysis = analyzeDockerfile(dockerfile);
result.setDockerfileAnalysis(analysis);
result.setSuccess(true);
} catch (Exception e) {
log.error("Dockerfile scan failed", e);
result.setError(e.getMessage());
result.setSuccess(false);
}
return result;
}
/**
* Monitor image for continuous security
*/
public MonitorSetupResult setupImageMonitoring(String imageName, String tag, String projectName) {
MonitorSetupResult result = new MonitorSetupResult();
result.setImageName(imageName);
result.setTag(tag);
result.setProjectName(projectName);
try {
// Setup monitoring via CLI
SnykCliService.MonitorResult cliResult = snykCliService.monitorContainerImage(imageName, tag, projectName);
result.setCliMonitorResult(cliResult);
// Setup monitoring via API
SnykApiClient.MonitorResult apiResult = snykApiClient.monitorContainerImage(imageName, tag, projectName);
result.setApiMonitorResult(apiResult);
result.setSuccess(true);
log.info("Image monitoring setup successfully: {}:{}", imageName, tag);
} catch (Exception e) {
log.error("Image monitoring setup failed", e);
result.setError(e.getMessage());
result.setSuccess(false);
}
return result;
}
/**
* Evaluate if image passes security gates
*/
public SecurityGateResult evaluateSecurityGates(SnykCliService.ScanResult scanResult) {
SecurityGateResult result = new SecurityGateResult();
result.setScanDate(LocalDateTime.now());
long criticalCount = scanResult.getCriticalCount();
long highCount = scanResult.getHighCount();
long mediumCount = scanResult.getMediumCount();
// Check against configured thresholds
boolean criticalPass = criticalCount <= snykConfig.getMaxCriticalIssues();
boolean highPass = highCount <= snykConfig.getMaxHighIssues();
boolean mediumPass = mediumCount <= snykConfig.getMaxMediumIssues();
result.setCriticalVulnerabilities(criticalCount);
result.setHighVulnerabilities(highCount);
result.setMediumVulnerabilities(mediumCount);
result.setCriticalGatePassed(criticalPass);
result.setHighGatePassed(highPass);
result.setMediumGatePassed(mediumPass);
result.setOverallPassed(criticalPass && highPass && mediumPass);
if (!result.isOverallPassed()) {
result.setFailureReason(buildFailureReason(criticalCount, highCount, mediumCount));
}
return result;
}
private ImageAnalysis analyzeImageMetadata(String imageName, String tag) {
ImageAnalysis analysis = new ImageAnalysis();
try {
// This would involve inspecting the image layers, packages, etc.
// For now, we'll provide a simplified analysis
analysis.setImageSize("Unknown"); // Would extract from Docker
analysis.setLayerCount(0); // Would count Docker layers
analysis.setPackageCount(0); // Would count OS packages
analysis.setBaseImage("Unknown"); // Would identify base image
// Check for common security issues
List<String> securityFindings = new ArrayList<>();
// These would be determined by actual image analysis
securityFindings.add("Image uses root user");
securityFindings.add("No non-root user configured");
securityFindings.add("Sensitive files in image");
analysis.setSecurityFindings(securityFindings);
} catch (Exception e) {
log.warn("Image metadata analysis failed", e);
}
return analysis;
}
private DockerfileAnalysis analyzeDockerfile(File dockerfile) {
DockerfileAnalysis analysis = new DockerfileAnalysis();
try {
List<String> lines = java.nio.file.Files.readAllLines(dockerfile.toPath());
analysis.setLineCount(lines.size());
List<String> bestPracticeViolations = new ArrayList<>();
List<String> securityIssues = new ArrayList<>();
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i).trim();
int lineNumber = i + 1;
// Check for security issues
if (line.startsWith("FROM") && line.contains(":latest")) {
securityIssues.add("Line " + lineNumber + ": Uses latest tag");
}
if (line.startsWith("RUN") && line.contains("apt-get update")) {
bestPracticeViolations.add("Line " + lineNumber + ": apt-get update without cleanup");
}
if (line.contains("curl") && line.contains("| sh")) {
securityIssues.add("Line " + lineNumber + ": Pipes curl to shell");
}
}
analysis.setBestPracticeViolations(bestPracticeViolations);
analysis.setSecurityIssues(securityIssues);
} catch (Exception e) {
log.warn("Dockerfile analysis failed", e);
}
return analysis;
}
private SecurityPosture evaluateSecurityPosture(SnykCliService.ScanResult cliResult,
SnykApiClient.ScanResult apiResult,
ImageAnalysis imageAnalysis) {
SecurityPosture posture = new SecurityPosture();
long totalVulnerabilities = cliResult.getVulnerabilities().size();
long criticalVulnerabilities = cliResult.getCriticalCount();
// Calculate security score (0-100)
int baseScore = 100;
baseScore -= criticalVulnerabilities * 10;
baseScore -= Math.min((totalVulnerabilities - criticalVulnerabilities) * 2, 50);
baseScore -= imageAnalysis.getSecurityFindings().size() * 5;
posture.setSecurityScore(Math.max(0, baseScore));
posture.setRiskLevel(calculateRiskLevel(baseScore));
posture.setRecommendations(generateRecommendations(cliResult, imageAnalysis));
return posture;
}
private String calculateRiskLevel(int score) {
if (score >= 80) return "LOW";
if (score >= 60) return "MEDIUM";
if (score >= 40) return "HIGH";
return "CRITICAL";
}
private List<String> generateRecommendations(SnykCliService.ScanResult scanResult, ImageAnalysis analysis) {
List<String> recommendations = new ArrayList<>();
if (scanResult.getCriticalCount() > 0) {
recommendations.add("Address critical vulnerabilities immediately");
}
if (scanResult.getHighCount() > 5) {
recommendations.add("Consider updating base image to reduce high severity vulnerabilities");
}
if (analysis.getSecurityFindings().contains("Image uses root user")) {
recommendations.add("Create and use non-root user in container");
}
return recommendations;
}
private String generateScanReport(ComprehensiveScanResult result) {
// Generate comprehensive HTML/JSON report
// This would be a detailed implementation
return "Scan report for " + result.getImageName() + ":" + result.getTag();
}
private List<String> extractImageTags(Image image) {
return Arrays.stream(image.getRepoTags())
.filter(tag -> !tag.contains("<none>"))
.collect(Collectors.toList());
}
private String buildFailureReason(long critical, long high, long medium) {
List<String> reasons = new ArrayList<>();
if (critical > snykConfig.getMaxCriticalIssues()) {
reasons.add("Critical vulnerabilities exceed threshold: " + critical);
}
if (high > snykConfig.getMaxHighIssues()) {
reasons.add("High vulnerabilities exceed threshold: " + high);
}
if (medium > snykConfig.getMaxMediumIssues()) {
reasons.add("Medium vulnerabilities exceed threshold: " + medium);
}
return String.join(", ", reasons);
}
// Data classes for results
@Data
public static class ComprehensiveScanResult {
private String imageName;
private String tag;
private LocalDateTime scanDate;
private boolean success = true;
private String error;
private SnykCliService.ScanResult cliScanResult;
private SnykApiClient.ScanResult apiScanResult;
private String sbom;
private ImageAnalysis imageAnalysis;
private SecurityPosture securityPosture;
private String report;
}
@Data
public static class BatchScanResult {
private int totalImages;
private int successCount;
private int failureCount;
private List<ComprehensiveScanResult> scanResults = new ArrayList<>();
private List<String> failedImages = new ArrayList<>();
}
@Data
public static class DockerfileScanResult {
private String dockerfilePath;
private LocalDateTime scanDate;
private boolean success = true;
private String error;
private SnykCliService.ScanResult dockerfileScanResult;
private SnykCliService.ScanResult baseImageScanResult;
private DockerfileAnalysis dockerfileAnalysis;
}
@Data
public static class MonitorSetupResult {
private String imageName;
private String tag;
private String projectName;
private boolean success = true;
private String error;
private SnykCliService.MonitorResult cliMonitorResult;
private SnykApiClient.MonitorResult apiMonitorResult;
}
@Data
public static class SecurityGateResult {
private LocalDateTime scanDate;
private long criticalVulnerabilities;
private long highVulnerabilities;
private long mediumVulnerabilities;
private boolean criticalGatePassed;
private boolean highGatePassed;
private boolean mediumGatePassed;
private boolean overallPassed;
private String failureReason;
}
@Data
public static class ImageAnalysis {
private String imageSize;
private int layerCount;
private int packageCount;
private String baseImage;
private List<String> securityFindings = new ArrayList<>();
}
@Data
public static class DockerfileAnalysis {
private int lineCount;
private List<String> bestPracticeViolations = new ArrayList<>();
private List<String> securityIssues = new ArrayList<>();
}
@Data
public static class SecurityPosture {
private int securityScore;
private String riskLevel;
private List<String> recommendations = new ArrayList<>();
}
}

Step 6: REST API Controllers

// src/main/java/com/company/snyk/controller/ContainerScanController.java
package com.company.snyk.controller;
import com.company.snyk.service.ContainerScanService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.io.File;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/container-scan")
@RequiredArgsConstructor
@Slf4j
public class ContainerScanController {
private final ContainerScanService containerScanService;
@PostMapping("/scan")
public ResponseEntity<Map<String, Object>> scanContainerImage(@RequestBody ScanRequest request) {
try {
var result = containerScanService.scanImageComprehensive(request.getImageName(), request.getTag());
return ResponseEntity.ok(Map.of(
"success", result.isSuccess(),
"image", result.getImageName() + ":" + result.getTag(),
"scanDate", result.getScanDate(),
"vulnerabilities", Map.of(
"critical", result.getCliScanResult().getCriticalCount(),
"high", result.getCliScanResult().getHighCount(),
"medium", result.getCliScanResult().getMediumCount(),
"low", result.getCliScanResult().getLowCount()
),
"securityScore", result.getSecurityPosture().getSecurityScore(),
"riskLevel", result.getSecurityPosture().getRiskLevel()
));
} catch (Exception e) {
log.error("Container scan failed", e);
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", e.getMessage()
));
}
}
@PostMapping("/scan/batch")
public ResponseEntity<Map<String, Object>> scanImagesBatch(@RequestBody BatchScanRequest request) {
try {
var result = containerScanService.scanImagesBatch(request.getImages());
return ResponseEntity.ok(Map.of(
"success", true,
"totalImages", result.getTotalImages(),
"successfulScans", result.getSuccessCount(),
"failedScans", result.getFailureCount(),
"results", result.getScanResults().stream()
.map(r -> Map.of(
"image", r.getImageName() + ":" + r.getTag(),
"success", r.isSuccess(),
"critical", r.getCliScanResult().getCriticalCount(),
"securityScore", r.getSecurityPosture().getSecurityScore()
))
.toList()
));
} catch (Exception e) {
log.error("Batch scan failed", e);
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", e.getMessage()
));
}
}
@PostMapping("/scan/dockerfile")
public ResponseEntity<Map<String, Object>> scanDockerfile(@RequestBody DockerfileScanRequest request) {
try {
File dockerfile = new File(request.getDockerfilePath());
if (!dockerfile.exists()) {
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", "Dockerfile not found: " + request.getDockerfilePath()
));
}
var result = containerScanService.scanDockerfile(dockerfile, request.getBaseImage());
return ResponseEntity.ok(Map.of(
"success", result.isSuccess(),
"dockerfile", result.getDockerfilePath(),
"vulnerabilities", result.getDockerfileScanResult().getVulnerabilities().size(),
"securityIssues", result.getDockerfileAnalysis().getSecurityIssues().size(),
"bestPracticeViolations", result.getDockerfileAnalysis().getBestPracticeViolations().size()
));
} catch (Exception e) {
log.error("Dockerfile scan failed", e);
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", e.getMessage()
));
}
}
@PostMapping("/monitor")
public ResponseEntity<Map<String, Object>> setupMonitoring(@RequestBody MonitorRequest request) {
try {
var result = containerScanService.setupImageMonitoring(
request.getImageName(), 
request.getTag(), 
request.getProjectName()
);
return ResponseEntity.ok(Map.of(
"success", result.isSuccess(),
"image", result.getImageName() + ":" + result.getTag(),
"project", result.getProjectName()
));
} catch (Exception e) {
log.error("Monitoring setup failed", e);
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", e.getMessage()
));
}
}
@GetMapping("/scan/local")
public ResponseEntity<Map<String, Object>> scanLocalImages() {
try {
var result = containerScanService.scanAllLocalImages();
return ResponseEntity.ok(Map.of(
"success", true,
"totalImages", result.getTotalImages(),
"successfulScans", result.getSuccessCount(),
"failedScans", result.getFailureCount()
));
} catch (Exception e) {
log.error("Local image scan failed", e);
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", e.getMessage()
));
}
}
@PostMapping("/security-gates")
public ResponseEntity<Map<String, Object>> evaluateSecurityGates(@RequestBody ScanResultRequest request) {
try {
var gateResult = containerScanService.evaluateSecurityGates(request.getScanResult());
return ResponseEntity.ok(Map.of(
"overallPassed", gateResult.isOverallPassed(),
"criticalPassed", gateResult.isCriticalGatePassed(),
"highPassed", gateResult.isHighGatePassed(),
"mediumPassed", gateResult.isMediumGatePassed(),
"vulnerabilities", Map.of(
"critical", gateResult.getCriticalVulnerabilities(),
"high", gateResult.getHighVulnerabilities(),
"medium", gateResult.getMediumVulnerabilities()
),
"failureReason", gateResult.getFailureReason()
));
} catch (Exception e) {
log.error("Security gates evaluation failed", e);
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", e.getMessage()
));
}
}
// Request DTOs
@Data
public static class ScanRequest {
private String imageName;
private String tag = "latest";
}
@Data
public static class BatchScanRequest {
private List<String> images;
}
@Data
public static class DockerfileScanRequest {
private String dockerfilePath;
private String baseImage;
}
@Data
public static class MonitorRequest {
private String imageName;
private String tag = "latest";
private String projectName;
}
@Data
public static class ScanResultRequest {
private com.company.snyk.service.SnykCliService.ScanResult scanResult;
}
}

Step 7: Application Configuration

application.yml

# application.yml
snyk:
api:
token: ${SNYK_API_TOKEN:}
url: https://api.snyk.io/v1
version: 2023-08-24
cli:
path: snyk
auto-update: true
scan:
severity-threshold: MEDIUM
fail-on-issues: true
max-critical-issues: 0
max-high-issues: 10
max-medium-issues: 50
reporting:
generate-sarif: true
generate-html: false
output-directory: ./snyk-reports
docker:
host: unix:///var/run/docker.sock
use-daemon: true
server:
port: 8080
logging:
level:
com.company.snyk: DEBUG
com.github.docker-java: INFO

Best Practices

  1. Security
  • Store Snyk API tokens securely (environment variables/secrets)
  • Use minimal permissions for Docker daemon access
  • Validate all inputs to prevent injection attacks
  1. Performance
  • Cache scan results when appropriate
  • Use parallel processing for batch scans
  • Implement rate limiting for API calls
  1. Reliability
  • Implement retry mechanisms for API calls
  • Handle Docker daemon connectivity issues
  • Provide fallback scanning methods
  1. Integration
  • Integrate with CI/CD pipelines
  • Generate actionable reports
  • Support multiple output formats (SARIF, HTML, JSON)

Conclusion

This Snyk Container scanning implementation provides:

  • Comprehensive Scanning: OS packages, application dependencies, configuration
  • Multiple Interfaces: REST API and CLI integration
  • Batch Operations: Scan multiple images efficiently
  • Security Gates: Configurable vulnerability thresholds
  • Detailed Reporting: Multiple output formats and comprehensive analysis

Key features:

  1. Dual Scanning: Both API and CLI integration for flexibility
  2. Comprehensive Analysis: Vulnerability scanning, SBOM generation, security posture assessment
  3. CI/CD Ready: Security gates and reporting suitable for pipeline integration
  4. Extensible Architecture: Easy to add new scanning capabilities and output formats

This solution enables Java applications to integrate container security scanning directly into their workflows, providing comprehensive security assessment and compliance validation for containerized applications.

Leave a Reply

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


Macro Nepal Helper