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