google-site-verification: google61fe8ba583a51912.html
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