Docker Scan Plugin in Java: Comprehensive Container Security Scanning

Docker Scan provides security scanning for Docker images, identifying vulnerabilities in your containerized Java applications.


Understanding Docker Scan

What is Docker Scan?

  • Built-in Docker security scanning tool
  • Identifies vulnerabilities in container images
  • Integrates with Snyk's vulnerability database
  • Provides remediation guidance

Key Features:

  • Image Scanning: Analyze Docker images for known vulnerabilities
  • Dependency Analysis: Scan application dependencies within containers
  • CI/CD Integration: Automated scanning in pipelines
  • Policy Enforcement: Define and enforce security policies
  • Remediation Guidance: Get fix recommendations

Setup and Dependencies

1. Docker Scan Installation
# Docker Scan comes built-in with Docker Desktop # For Linux, install separately curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" sudo apt-get update sudo apt-get install docker-scan # Verify installation docker scan --version
2. Maven Dependencies for Integration
<properties> <docker-maven-plugin.version>0.40.0</docker-maven-plugin.version> <jib-maven-plugin.version>3.3.1</jib-maven-plugin.version> <testcontainers.version>1.18.3</testcontainers.version> </properties> <dependencies> <!-- Docker Java Client --> <dependency> <groupId>com.github.docker-java</groupId> <artifactId>docker-java</artifactId> <version>3.3.0</version> </dependency> <!-- TestContainers for integration testing --> <dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> <version>${testcontainers.version}</version> <scope>test</scope> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> </dependencies> <build> <plugins> <!-- Docker Maven Plugin --> <plugin> <groupId>io.fabric8</groupId> <artifactId>docker-maven-plugin</artifactId> <version>${docker-maven-plugin.version}</version> </plugin> <!-- Jib for container building --> <plugin> <groupId>com.google.cloud.tools</groupId> <artifactId>jib-maven-plugin</artifactId> <version>${jib-maven-plugin.version}</version> </plugin> </plugins> </build>
3. Gradle Configuration
plugins { id 'com.google.cloud.tools.jib' version '3.3.1' id 'com.bmuschko.docker-remote-api' version '9.3.2' } dependencies { implementation 'com.github.docker-java:docker-java:3.3.0' testImplementation 'org.testcontainers:testcontainers:1.18.3' }

Docker Scan Integration

1. Basic Docker Scan Service
package com.yourapp.docker; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; @Service public class DockerScanService { private static final Logger logger = LoggerFactory.getLogger(DockerScanService.class); private final ObjectMapper objectMapper = new ObjectMapper(); public ScanResult scanImage(String imageName) throws IOException, InterruptedException { return scanImage(imageName, new ScanOptions()); } public ScanResult scanImage(String imageName, ScanOptions options) throws IOException, InterruptedException { List<String> command = buildScanCommand(imageName, options); logger.info("Executing Docker scan: {}", String.join(" ", command)); ProcessBuilder processBuilder = new ProcessBuilder(command); Process process = processBuilder.start(); // Read output StringBuilder output = new StringBuilder(); try (BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { output.append(line).append("\n"); } } // Read errors StringBuilder errors = new StringBuilder(); try (BufferedReader errorReader = new BufferedReader( new InputStreamReader(process.getErrorStream()))) { String line; while ((line = errorReader.readLine()) != null) { errors.append(line).append("\n"); } } int exitCode = process.waitFor(); String result = output.toString(); if (exitCode != 0) { throw new DockerScanException("Docker scan failed: " + errors.toString()); } return parseScanResult(result, imageName); } private List<String> buildScanCommand(String imageName, ScanOptions options) { List<String> command = new ArrayList<>(); command.add("docker"); command.add("scan"); if (options.isJsonOutput()) { command.add("--json"); } if (options.getSeverityThreshold() != null) { command.add("--severity"); command.add(options.getSeverityThreshold()); } if (options.isDependencyTree()) { command.add("--dependency-tree"); } if (options.isExcludeBase()) { command.add("--exclude-base"); } if (options.getFile() != null) { command.add("--file"); command.add(options.getFile()); } command.add(imageName); return command; } private ScanResult parseScanResult(String jsonResult, String imageName) throws IOException { JsonNode root = objectMapper.readTree(jsonResult); ScanResult result = new ScanResult(); result.setImageName(imageName); result.setScanTimestamp(root.path("scanTimestamp").asText()); result.setVulnerabilities(new ArrayList<>()); JsonNode vulnerabilities = root.path("vulnerabilities"); if (vulnerabilities.isArray()) { for (JsonNode vuln : vulnerabilities) { Vulnerability vulnerability = parseVulnerability(vuln); result.getVulnerabilities().add(vulnerability); } } // Calculate summary calculateSummary(result); return result; } private Vulnerability parseVulnerability(JsonNode vuln) { Vulnerability vulnerability = new Vulnerability(); vulnerability.setId(vuln.path("id").asText()); vulnerability.setTitle(vuln.path("title").asText()); vulnerability.setDescription(vuln.path("description").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()); vulnerability.setCvssScore(vuln.path("cvssScore").asDouble()); vulnerability.setCve(vuln.path("identifiers").path("CVE").asText()); return vulnerability; } private void calculateSummary(ScanResult result) { Summary summary = new Summary(); List<Vulnerability> vulnerabilities = result.getVulnerabilities(); summary.setTotal(vulnerabilities.size()); summary.setCritical((int) vulnerabilities.stream() .filter(v -> "critical".equalsIgnoreCase(v.getSeverity())) .count()); summary.setHigh((int) vulnerabilities.stream() .filter(v -> "high".equalsIgnoreCase(v.getSeverity())) .count()); summary.setMedium((int) vulnerabilities.stream() .filter(v -> "medium".equalsIgnoreCase(v.getSeverity())) .count()); summary.setLow((int) vulnerabilities.stream() .filter(v -> "low".equalsIgnoreCase(v.getSeverity())) .count()); result.setSummary(summary); } } @Data public class ScanResult { private String imageName; private String scanTimestamp; private List<Vulnerability> vulnerabilities; private Summary summary; private boolean compliant; } @Data public class Vulnerability { private String id; private String title; private String description; private String severity; private String packageName; private String version; private String fixedIn; private double cvssScore; private String cve; private List<String> references; } @Data public class Summary { private int total; private int critical; private int high; private int medium; private int low; } @Data public class ScanOptions { private boolean jsonOutput = true; private String severityThreshold; private boolean dependencyTree = false; private boolean excludeBase = false; private String file; public static ScanOptions defaultOptions() { return new ScanOptions(); } public static ScanOptions strictOptions() { ScanOptions options = new ScanOptions(); options.setSeverityThreshold("high"); options.setJsonOutput(true); return options; } } public class DockerScanException extends RuntimeException { public DockerScanException(String message) { super(message); } public DockerScanException(String message, Throwable cause) { super(message, cause); } }
2. Advanced Security Policy Engine
@Service public class SecurityPolicyEngine { private final SecurityPolicy policy; public PolicyEvaluation evaluateScanResult(ScanResult scanResult) { PolicyEvaluation evaluation = new PolicyEvaluation(); evaluation.setScanResult(scanResult); evaluation.setViolations(new ArrayList<>()); // Check critical vulnerabilities if (policy.getMaxCritical() >= 0 && scanResult.getSummary().getCritical() > policy.getMaxCritical()) { evaluation.getViolations().add( new PolicyViolation("CRITICAL_VULNERABILITIES", "Exceeded maximum critical vulnerabilities: " + scanResult.getSummary().getCritical() + " > " + policy.getMaxCritical()) ); } // Check high vulnerabilities if (policy.getMaxHigh() >= 0 && scanResult.getSummary().getHigh() > policy.getMaxHigh()) { evaluation.getViolations().add( new PolicyViolation("HIGH_VULNERABILITIES", "Exceeded maximum high vulnerabilities: " + scanResult.getSummary().getHigh() + " > " + policy.getMaxHigh()) ); } // Check specific prohibited vulnerabilities checkProhibitedVulnerabilities(scanResult, evaluation); // Check CVSS score threshold checkCvssThreshold(scanResult, evaluation); evaluation.setCompliant(evaluation.getViolations().isEmpty()); return evaluation; } private void checkProhibitedVulnerabilities(ScanResult scanResult, PolicyEvaluation evaluation) { for (Vulnerability vuln : scanResult.getVulnerabilities()) { if (policy.getProhibitedCves().contains(vuln.getCve())) { evaluation.getViolations().add( new PolicyViolation("PROHIBITED_CVE", "Prohibited CVE found: " + vuln.getCve()) ); } } } private void checkCvssThreshold(ScanResult scanResult, PolicyEvaluation evaluation) { for (Vulnerability vuln : scanResult.getVulnerabilities()) { if (vuln.getCvssScore() >= policy.getMaxCvssScore()) { evaluation.getViolations().add( new PolicyViolation("CVSS_THRESHOLD", "Vulnerability exceeds CVSS threshold: " + vuln.getCve() + " (" + vuln.getCvssScore() + ")") ); } } } public boolean canProceedToDeployment(PolicyEvaluation evaluation) { return evaluation.isCompliant() || policy.isAllowOverride(); } } @Data public class SecurityPolicy { private String name; private String version; private int maxCritical = 0; private int maxHigh = 5; private int maxMedium = 20; private int maxLow = -1; // -1 means no limit private double maxCvssScore = 8.0; private Set<String> prohibitedCves = new HashSet<>(); private boolean allowOverride = false; private List<String> allowedExceptions = new ArrayList<>(); public static SecurityPolicy strictPolicy() { SecurityPolicy policy = new SecurityPolicy(); policy.setName("Strict Security Policy"); policy.setMaxCritical(0); policy.setMaxHigh(0); policy.setMaxMedium(5); policy.setMaxCvssScore(7.0); policy.setAllowOverride(false); return policy; } public static SecurityPolicy lenientPolicy() { SecurityPolicy policy = new SecurityPolicy(); policy.setName("Lenient Security Policy"); policy.setMaxCritical(1); policy.setMaxHigh(10); policy.setMaxMedium(50); policy.setMaxCvssScore(9.0); policy.setAllowOverride(true); return policy; } } @Data public class PolicyEvaluation { private ScanResult scanResult; private boolean compliant; private List<PolicyViolation> violations; private LocalDateTime evaluationTime; public PolicyEvaluation() { this.evaluationTime = LocalDateTime.now(); this.violations = new ArrayList<>(); } } @Data public class PolicyViolation { private String type; private String message; private String severity; public PolicyViolation(String type, String message) { this.type = type; this.message = message; this.severity = "HIGH"; } }

Dockerfile Security Best Practices

1. Secure Base Image Configuration
# BAD: Using latest tag and root user FROM openjdk:latest RUN apt-get update && apt-get install -y curl COPY . /app RUN chmod +x /app/start.sh USER root CMD ["/app/start.sh"] # GOOD: Secure Dockerfile for Java application # Use specific version and non-root user FROM eclipse-temurin:17.0.8_7-jre-jammy@sha256:a1ed6eeb1c6e... # Security scanning labels LABEL org.opencontainers.image.source="https://github.com/your-org/your-app" LABEL org.opencontainers.image.licenses="Apache-2.0" LABEL com.snyk.security.scan.enabled="true" # Install security updates RUN apt-get update && \ apt-get upgrade -y && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* # Create non-root user RUN groupadd -r appuser && useradd -r -g appuser appuser # Create application directory with proper permissions RUN mkdir -p /app && chown appuser:appuser /app # Copy application COPY --chown=appuser:appuser target/your-app.jar /app/your-app.jar COPY --chown=appuser:appuser entrypoint.sh /app/entrypoint.sh # Set working directory WORKDIR /app # Switch to non-root user USER appuser # Use exec form for CMD CMD ["/app/entrypoint.sh"] # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8080/health || exit 1 # Security best practices: # 1. Use specific base image with digest # 2. Regular security updates # 3. Non-root user # 4. Minimal layers # 5. Health checks # 6. Proper file permissions
2. Multi-stage Build for Security
# Multi-stage build to reduce attack surface FROM eclipse-temurin:17.0.8_7-jdk-jammy as builder # Install build dependencies RUN apt-get update && \ apt-get install -y maven && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* WORKDIR /build COPY pom.xml . COPY src ./src # Build application RUN mvn clean package -DskipTests # Runtime stage FROM eclipse-temurin:17.0.8_7-jre-jammy@sha256:a1ed6eeb1c6e... # Security hardening RUN apt-get update && \ apt-get upgrade -y && \ apt-get install -y --no-install-recommends \ ca-certificates \ curl && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* && \ groupadd -r appuser && useradd -r -g appuser appuser # Copy only built artifact from builder stage COPY --from=builder /build/target/your-app.jar /app/your-app.jar COPY --chown=appuser:appuser entrypoint.sh /app/entrypoint.sh WORKDIR /app USER appuser # Security context RUN chmod 500 entrypoint.sh && \ chmod 400 your-app.jar EXPOSE 8080 CMD ["/app/entrypoint.sh"]
3. Security-Focused Entrypoint Script
#!/bin/sh # entrypoint.sh - Secure entrypoint for Java application set -euo pipefail # Security configurations export JAVA_OPTS="${JAVA_OPTS:-} \ -Djava.security.egd=file:/dev/./urandom \ -Djava.awt.headless=true \ -Dfile.encoding=UTF-8 \ -Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL" # Memory limits based on container constraints if [ -n "${JAVA_MAX_MEM:-}" ]; then JAVA_OPTS="$JAVA_OPTS -Xmx${JAVA_MAX_MEM}" fi # Security manager (optional) if [ "${ENABLE_SECURITY_MANAGER:-false}" = "true" ]; then JAVA_OPTS="$JAVA_OPTS -Djava.security.manager -Djava.security.policy=/app/security.policy" fi # Debug logging for security incidents if [ "${ENABLE_SECURITY_DEBUG:-false}" = "true" ]; then JAVA_OPTS="$JAVA_OPTS -Djava.security.debug=all" fi echo "Starting application with security options: $JAVA_OPTS" # Use exec to replace shell process with Java process exec java $JAVA_OPTS -jar /app/your-app.jar "$@"

CI/CD Integration

1. GitHub Actions Workflow
name: Docker Security Scan on: push: branches: [ main, develop ] pull_request: branches: [ main ] schedule: - cron: '0 6 * * 1' # Weekly scan jobs: security-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up JDK 17 uses: actions/setup-java@v3 with: java-version: '17' distribution: 'temurin' cache: 'maven' - name: Build application run: mvn clean package -DskipTests - name: Build Docker image run: | docker build -t your-app:${{ github.sha }} . docker tag your-app:${{ github.sha }} your-app:latest - name: Run Docker Scan run: | docker scan --accept-license --file ./Dockerfile \ --severity high --json your-app:${{ github.sha }} > scan-report.json - name: Analyze Scan Results run: | java -cp target/your-app.jar com.yourapp.docker.ScanAnalyzer \ --report scan-report.json \ --policy security-policy.json - name: Upload Security Report uses: actions/upload-artifact@v3 with: name: security-scan-report path: | scan-report.json policy-evaluation.json - name: Fail on Critical Vulnerabilities run: | CRITICAL_COUNT=$(jq '.summary.critical' scan-report.json) if [ "$CRITICAL_COUNT" -gt 0 ]; then echo "Critical vulnerabilities found: $CRITICAL_COUNT" exit 1 fi trivy-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@master with: image-ref: 'your-app:latest' format: 'sarif' output: 'trivy-results.sarif' severity: 'CRITICAL,HIGH' - name: Upload Trivy results uses: github/codeql-action/upload-sarif@v2 with: sarif_file: 'trivy-results.sarif'
2. Jenkins Pipeline
pipeline { agent any tools { maven 'Maven-3.8' jdk 'JDK17' } environment { DOCKER_SCAN_SUGGEST = 'false' } stages { stage('Build') { steps { sh 'mvn clean package -DskipTests' } } stage('Build Docker Image') { steps { script { docker.build("your-app:${env.BUILD_ID}") } } } stage('Security Scan') { steps { script { // Run Docker Scan sh ''' docker scan --json your-app:${BUILD_ID} > scan-report.json ''' // Analyze results sh ''' java -cp target/your-app.jar com.yourapp.docker.ScanAnalyzer \ --report scan-report.json \ --policy strict-policy.json ''' // Check for policy violations def scanResult = readJSON file: 'scan-report.json' def criticalCount = scanResult.summary.critical if (criticalCount > 0) { error "Found ${criticalCount} critical vulnerabilities. Build failed." } } } } stage('Push to Registry') { when { expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' } } steps { script { docker.withRegistry('https://registry.yourcompany.com', 'docker-credentials') { docker.image("your-app:${env.BUILD_ID}").push() docker.image("your-app:${env.BUILD_ID}").push('latest') } } } } } post { always { publishHTML([ allowMissing: false, alwaysLinkToLastBuild: true, keepAll: true, reportDir: '', reportFiles: 'scan-report.html', reportName: 'Docker Security Scan Report' ]) emailext ( subject: "Docker Security Scan: ${env.JOB_NAME} - Build ${env.BUILD_NUMBER}", body: """ Security scan completed for ${env.JOB_NAME} Build: ${env.BUILD_URL} Scan Report: ${env.BUILD_URL}security-scan/ """, to: '[email protected]' ) } failure { slackSend( channel: '#security-alerts', message: "Security scan failed for ${env.JOB_NAME}. Check ${env.BUILD_URL}" ) } } }
3. GitLab CI Pipeline
stages: - build - test - security-scan - deploy variables: MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository" docker-security-scan: stage: security-scan image: docker:latest services: - docker:dind before_script: - apk add --no-cache openjdk11 maven - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY script: - mvn clean package -DskipTests - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . - docker scan --accept-license --json $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA > scan-report.json - docker scan --dependency-tree $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA - | if [ $(jq '.summary.critical' scan-report.json) -gt 0 ]; then echo "Critical vulnerabilities found. Failing pipeline." exit 1 fi artifacts: when: always paths: - scan-report.json reports: sast: scan-report.json only: - main - develop - merge_requests trivy-scan: stage: security-scan image: name: aquasec/trivy:0.45.1 entrypoint: [""] variables: TRIVY_USERNAME: $CI_REGISTRY_USER TRIVY_PASSWORD: $CI_REGISTRY_PASSWORD TRIVY_NON_SSL: "true" script: - trivy image --exit-code 0 --format template --template "@contrib/gitlab.tpl" --output gl-dependency-scanning-report.json $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA - trivy image --exit-code 1 --severity CRITICAL,HIGH $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA artifacts: when: always reports: dependency_scanning: gl-dependency-scanning-report.json

Advanced Scanning Features

1. Custom Vulnerability Database
@Service public class CustomVulnerabilityService { private final VulnerabilityDatabase customDatabase; private final DockerScanService dockerScanService; public EnhancedScanResult enhancedScan(String imageName) throws Exception { // Get standard Docker scan results ScanResult standardResult = dockerScanService.scanImage(imageName); // Enhance with custom vulnerability data EnhancedScanResult enhancedResult = new EnhancedScanResult(standardResult); // Add custom vulnerability checks checkCustomVulnerabilities(enhancedResult); // Check for business-specific risks checkBusinessRisks(enhancedResult); // Generate remediation advice generateRemediationAdvice(enhancedResult); return enhancedResult; } private void checkCustomVulnerabilities(EnhancedScanResult result) { for (Vulnerability vuln : result.getVulnerabilities()) { CustomVulnerabilityInfo customInfo = customDatabase.findByCve(vuln.getCve()); if (customInfo != null) { vuln.setCustomRiskScore(customInfo.getRiskScore()); vuln.setBusinessImpact(customInfo.getBusinessImpact()); vuln.setRemediationDeadline(customInfo.getRemediationDeadline()); } } } private void checkBusinessRisks(EnhancedScanResult result) { // Check for dependencies with known business risks List<BusinessRisk> businessRisks = new ArrayList<>(); for (Vulnerability vuln : result.getVulnerabilities()) { if (isHighBusinessRisk(vuln)) { businessRisks.add(createBusinessRisk(vuln)); } } result.setBusinessRisks(businessRisks); } private boolean isHighBusinessRisk(Vulnerability vuln) { return vuln.getCvssScore() >= 7.0 || "critical".equals(vuln.getSeverity()) || isInProductionCriticalPath(vuln.getPackageName()); } private void generateRemediationAdvice(EnhancedScanResult result) { List<RemediationAdvice> adviceList = new ArrayList<>(); for (Vulnerability vuln : result.getVulnerabilities()) { if (vuln.getFixedIn() != null && !vuln.getFixedIn().isEmpty()) { RemediationAdvice advice = new RemediationAdvice(); advice.setVulnerabilityId(vuln.getId()); advice.setPackageName(vuln.getPackageName()); advice.setCurrentVersion(vuln.getVersion()); advice.setFixedVersion(vuln.getFixedIn()); advice.setAction(generateAction(vuln)); advice.setPriority(calculatePriority(vuln)); adviceList.add(advice); } } result.setRemediationAdvice(adviceList); } } @Data public class EnhancedScanResult extends ScanResult { private List<BusinessRisk> businessRisks; private List<RemediationAdvice> remediationAdvice; private ComplianceStatus complianceStatus; public EnhancedScanResult(ScanResult baseResult) { this.setImageName(baseResult.getImageName()); this.setScanTimestamp(baseResult.getScanTimestamp()); this.setVulnerabilities(baseResult.getVulnerabilities()); this.setSummary(baseResult.getSummary()); this.businessRisks = new ArrayList<>(); this.remediationAdvice = new ArrayList<>(); } } @Data public class BusinessRisk { private String vulnerabilityId; private String riskDescription; private String businessImpact; private String mitigationStrategy; private String owner; } @Data public class RemediationAdvice { private String vulnerabilityId; private String packageName; private String currentVersion; private String fixedVersion; private String action; private String priority; }
2. Historical Scan Analysis
@Service public class ScanHistoryService { private final ScanResultRepository repository; public TrendAnalysis analyzeVulnerabilityTrends(String imageName, Duration period) { LocalDateTime endDate = LocalDateTime.now(); LocalDateTime startDate = endDate.minus(period); List<ScanResult> historicalScans = repository.findByImageNameAndDateRange(imageName, startDate, endDate); TrendAnalysis analysis = new TrendAnalysis(); analysis.setImageName(imageName); analysis.setPeriod(period); analysis.setScanCount(historicalScans.size()); // Calculate trends calculateVulnerabilityTrends(analysis, historicalScans); calculateRemediationEfficiency(analysis, historicalScans); calculateRiskScoreTrends(analysis, historicalScans); return analysis; } private void calculateVulnerabilityTrends(TrendAnalysis analysis, List<ScanResult> scans) { if (scans.size() < 2) return; ScanResult oldest = scans.get(0); ScanResult newest = scans.get(scans.size() - 1); analysis.setCriticalTrend(newest.getSummary().getCritical() - oldest.getSummary().getCritical()); analysis.setHighTrend(newest.getSummary().getHigh() - oldest.getSummary().getHigh()); analysis.setTotalTrend(newest.getSummary().getTotal() - oldest.getSummary().getTotal()); } public ComplianceReport generateComplianceReport(String imageName) { List<ScanResult> recentScans = repository.findTop10ByImageNameOrderByScanTimestampDesc(imageName); ComplianceReport report = new ComplianceReport(); report.setImageName(imageName); report.setGeneratedDate(LocalDateTime.now()); if (!recentScans.isEmpty()) { ScanResult latest = recentScans.get(0); report.setLatestScan(latest); report.setCompliant(isCompliant(latest)); report.setDaysSinceLastScan( Duration.between(latest.getScanTimestamp(), LocalDateTime.now()).toDays()); } report.setTrendAnalysis(analyzeVulnerabilityTrends(imageName, Duration.ofDays(30))); return report; } } @Data public class TrendAnalysis { private String imageName; private Duration period; private int scanCount; private int criticalTrend; private int highTrend; private int totalTrend; private double remediationEfficiency; private double riskScoreTrend; private List<String> recommendations; } @Data public class ComplianceReport { private String imageName; private LocalDateTime generatedDate; private ScanResult latestScan; private boolean compliant; private long daysSinceLastScan; private TrendAnalysis trendAnalysis; private List<PolicyViolation> violations; }

Remediation Automation

1. Automated Fix Suggestions
@Service public class RemediationService { private final DockerfileService dockerfileService; private final DependencyUpdateService dependencyService; public RemediationPlan generateRemediationPlan(ScanResult scanResult) { RemediationPlan plan = new RemediationPlan(); plan.setGeneratedDate(LocalDateTime.now()); plan.setImageName(scanResult.getImageName()); List<RemediationAction> actions = new ArrayList<>(); for (Vulnerability vuln : scanResult.getVulnerabilities()) { if (vuln.getFixedIn() != null && !vuln.getFixedIn().isEmpty()) { RemediationAction action = createRemediationAction(vuln); actions.add(action); } } // Sort by priority actions.sort(Comparator.comparing(RemediationAction::getPriority).reversed()); plan.setActions(actions); plan.setEstimatedEffort(calculateEffort(actions)); return plan; } private RemediationAction createRemediationAction(Vulnerability vuln) { RemediationAction action = new RemediationAction(); action.setVulnerabilityId(vuln.getId()); action.setPackageName(vuln.getPackageName()); action.setCurrentVersion(vuln.getVersion()); action.setTargetVersion(vuln.getFixedIn()); action.setSeverity(vuln.getSeverity()); action.setPriority(calculatePriority(vuln)); action.setActionType(determineActionType(vuln)); action.setInstructions(generateInstructions(vuln)); return action; } private ActionType determineActionType(Vulnerability vuln) { if (vuln.getPackageName().startsWith("base-image")) { return ActionType.BASE_IMAGE_UPDATE; } else if (isApplicationDependency(vuln.getPackageName())) { return ActionType.DEPENDENCY_UPDATE; } else { return ActionType.SYSTEM_PACKAGE_UPDATE; } } private String generateInstructions(Vulnerability vuln) { switch (determineActionType(vuln)) { case BASE_IMAGE_UPDATE: return "Update base image in Dockerfile to a secure version"; case DEPENDENCY_UPDATE: return String.format("Update %s from %s to %s in pom.xml", vuln.getPackageName(), vuln.getVersion(), vuln.getFixedIn()); case SYSTEM_PACKAGE_UPDATE: return String.format("Update system package %s in Dockerfile", vuln.getPackageName()); default: return "Manual remediation required"; } } public void applyRemediation(RemediationAction action) throws Exception { switch (action.getActionType()) { case BASE_IMAGE_UPDATE: dockerfileService.updateBaseImage(action.getTargetVersion()); break; case DEPENDENCY_UPDATE: dependencyService.updateDependency( action.getPackageName(), action.getTargetVersion()); break; case SYSTEM_PACKAGE_UPDATE: dockerfileService.updateSystemPackage( action.getPackageName(), action.getTargetVersion()); break; } } } @Data public class RemediationPlan { private String imageName; private LocalDateTime generatedDate; private List<RemediationAction> actions; private String estimatedEffort; private List<String> prerequisites; } @Data public class RemediationAction { private String vulnerabilityId; private String packageName; private String currentVersion; private String targetVersion; private String severity; private int priority; private ActionType actionType; private String instructions; private boolean automated; } public enum ActionType { BASE_IMAGE_UPDATE, DEPENDENCY_UPDATE, SYSTEM_PACKAGE_UPDATE, CONFIGURATION_CHANGE, MANUAL_REVIEW }

Best Practices

1. Security Scanning Checklist
public class DockerScanChecklist { public static final List<String> SECURITY_CHECKS = Arrays.asList( "1. Use specific base image tags with digests", "2. Regularly update base images", "3. Run as non-root user", "4. Use multi-stage builds", "5. Scan images in CI/CD pipeline", "6. Implement security policies", "7. Monitor for new vulnerabilities", "8. Automate remediation where possible", "9. Maintain scan history and trends", "10. Train developers on secure container practices" ); public static ScanConfiguration secureConfiguration() { ScanConfiguration config = new ScanConfiguration(); config.setSeverityThreshold("high"); config.setDependencyTree(true); config.setExcludeBase(false); config.setJsonOutput(true); config.setFailOnCritical(true); return config; } }
2. Continuous Monitoring
@Service public class ContinuousMonitoringService { @Scheduled(cron = "0 0 6 * * *") // Daily at 6 AM public void dailySecurityScan() { List<String> productionImages = getProductionImages(); for (String image : productionImages) { try { ScanResult result = dockerScanService.scanImage(image); PolicyEvaluation evaluation = policyEngine.evaluateScanResult(result); if (!evaluation.isCompliant()) { sendSecurityAlert(image, evaluation); } // Store results for trending scanHistoryService.storeScanResult(result); } catch (Exception e) { logger.error("Failed to scan image: {}", image, e); } } } private void sendSecurityAlert(String image, PolicyEvaluation evaluation) { SecurityAlert alert = new SecurityAlert(); alert.setImageName(image); alert.setEvaluation(evaluation); alert.setGeneratedAt(LocalDateTime.now()); alert.setSeverity(calculateAlertSeverity(evaluation)); // Send to security team alertService.sendAlert(alert); // Create JIRA ticket for critical issues if (alert.getSeverity() == AlertSeverity.CRITICAL) { jiraService.createSecurityTicket(alert); } } }

Conclusion

Docker Scan Plugin integration provides:

  • Comprehensive vulnerability scanning for container images
  • Policy-based security enforcement in CI/CD pipelines
  • Automated remediation guidance and fix suggestions
  • Historical trend analysis for security posture
  • Continuous monitoring of production images

By implementing the patterns and configurations shown above, you can establish a robust container security practice that integrates seamlessly with your Java development workflow, ensuring secure container deployments and proactive vulnerability management.

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.

Leave a Reply

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


Macro Nepal Helper