In modern cloud-native development, container images stored in Amazon Elastic Container Registry (ECR) are the building blocks of your applications. Ensuring these images are free from known vulnerabilities is a critical step in the software supply chain security. While AWS provides powerful built-in scanning capabilities, integrating these scans into your Java-based CI/CD pipelines and applications is essential for automation and governance.
This article explores how to implement and leverage ECR Image Scanning programmatically using the AWS SDK for Java, enabling you to build secure, automated container workflows.
Understanding ECR Image Scanning
Amazon ECR integrates with vulnerability scanning partners (like AWS Marketplace sellers) to provide automated, push-time scanning of your container images.
- Basic Scanning: Scans images on push and provides a list of findings.
- Enhanced Scanning: Leverages additional security partners for more comprehensive vulnerability detection (may incur extra costs).
The scan results include:
- Vulnerability severity levels (
INFORMATIONAL,LOW,MEDIUM,HIGH,CRITICAL,UNDEFINED) - CVE IDs and descriptions
- CVSS scores
- Affected packages and versions
- Remediation recommendations
Key AWS Java SDK Dependencies
To work with ECR programmatically, you need the AWS SDK for Java v2.
Maven Dependency:
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>ecr</artifactId>
<version>2.20.0</version> <!-- Always use the latest version -->
</dependency>
Gradle Dependency:
implementation 'software.amazon.awssdk:ecr:2.20.0'
Core Java Operations for ECR Image Scanning
1. Starting an Image Scan
You can trigger a scan manually for an existing image in your repository.
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ecr.EcrClient;
import software.amazon.awssdk.services.ecr.model.StartImageScanRequest;
import software.amazon.awssdk.services.ecr.model.StartImageScanResponse;
import software.amazon.awssdk.services.ecr.model.EcrException;
public class ECRScanManager {
private final EcrClient ecrClient;
public ECRScanManager() {
this.ecrClient = EcrClient.builder()
.region(Region.US_EAST_1) // Specify your region
.credentialsProvider(DefaultCredentialsProvider.create())
.build();
}
public void startImageScan(String repositoryName, String imageTag) {
try {
StartImageScanRequest request = StartImageScanRequest.builder()
.repositoryName(repositoryName)
.imageId(b -> b.imageTag(imageTag))
.build();
StartImageScanResponse response = ecrClient.startImageScan(request);
if (response.sdkHttpResponse().isSuccessful()) {
System.out.println("Successfully started scan for: " +
repositoryName + ":" + imageTag);
}
} catch (EcrException e) {
System.err.println("Error starting image scan: " + e.awsErrorDetails().errorMessage());
}
}
}
2. Retrieving Scan Findings
After initiating a scan (or for push-time automated scans), you can retrieve the findings.
import software.amazon.awssdk.services.ecr.model.DescribeImageScanFindingsRequest;
import software.amazon.awssdk.services.ecr.model.DescribeImageScanFindingsResponse;
import software.amazon.awssdk.services.ecr.model.ImageScanFindings;
import software.amazon.awssdk.services.ecr.model.FindingSeverity;
import software.amazon.awssdk.services.ecr.model.ImageScanFinding;
import java.util.List;
import java.util.Map;
public class ECRFindingsRetriever {
private final EcrClient ecrClient;
public ECRFindingsRetriever(EcrClient ecrClient) {
this.ecrClient = ecrClient;
}
public void printScanFindings(String repositoryName, String imageTag) {
try {
DescribeImageScanFindingsRequest request = DescribeImageScanFindingsRequest.builder()
.repositoryName(repositoryName)
.imageId(b -> b.imageTag(imageTag))
.maxResults(100) // Adjust as needed
.build();
DescribeImageScanFindingsResponse response = ecrClient.describeImageScanFindings(request);
ImageScanFindings findings = response.imageScanFindings();
if (findings != null) {
System.out.println("=== Scan Findings for " + repositoryName + ":" + imageTag + " ===");
System.out.println("Vulnerabilities found: " + findings.findingSeverityCounts());
System.out.println("Scan completed at: " + findings.imageScanCompletedAt());
// Print detailed findings
printDetailedFindings(findings.findings());
} else {
System.out.println("No findings available or scan still in progress.");
}
} catch (EcrException e) {
System.err.println("Error retrieving scan findings: " + e.awsErrorDetails().errorMessage());
}
}
private void printDetailedFindings(List<ImageScanFinding> findings) {
for (ImageScanFinding finding : findings) {
System.out.println("\n--- Finding ---");
System.out.println("Name: " + finding.name());
System.out.println("Severity: " + finding.severity());
System.out.println("URI: " + finding.uri());
System.out.println("Description: " + finding.description());
if (finding.attributes() != null) {
finding.attributes().forEach(attr ->
System.out.println("Attribute: " + attr.key() + " = " + attr.value()));
}
}
}
}
3. Waiting for Scan Completion and Evaluating Results
Scans take time to complete. Here's how to poll for completion and evaluate against your security policy.
import software.amazon.awssdk.services.ecr.model.EcrException;
import software.amazon.awssdk.services.ecr.model.FindingSeverity;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class ECRScanEvaluator {
private final EcrClient ecrClient;
public ECRScanEvaluator(EcrClient ecrClient) {
this.ecrClient = ecrClient;
}
public boolean waitForScanCompletion(String repositoryName, String imageTag,
int maxWaitMinutes) throws InterruptedException {
long endTime = System.currentTimeMillis() + Duration.ofMinutes(maxWaitMinutes).toMillis();
while (System.currentTimeMillis() < endTime) {
DescribeImageScanFindingsResponse response = ecrClient.describeImageScanFindings(
DescribeImageScanFindingsRequest.builder()
.repositoryName(repositoryName)
.imageId(b -> b.imageTag(imageTag))
.build()
);
if (response.imageScanStatus().status().toString().equals("COMPLETE")) {
return true;
}
System.out.println("Scan status: " + response.imageScanStatus().status() +
", description: " + response.imageScanStatus().description());
TimeUnit.SECONDS.sleep(30); // Wait 30 seconds before checking again
}
return false;
}
public boolean passesSecurityPolicy(String repositoryName, String imageTag,
boolean failOnHighSeverity) {
try {
DescribeImageScanFindingsResponse response = ecrClient.describeImageScanFindings(
DescribeImageScanFindingsRequest.builder()
.repositoryName(repositoryName)
.imageId(b -> b.imageTag(imageTag))
.build()
);
ImageScanFindings findings = response.imageScanFindings();
if (findings == null) {
System.out.println("No findings available.");
return false;
}
Map<FindingSeverity, Integer> severityCounts = findings.findingSeverityCounts();
// Example policy: Fail if any CRITICAL findings, or if failOnHighSeverity is true and HIGH findings exist
boolean hasCritical = severityCounts.getOrDefault(FindingSeverity.CRITICAL, 0) > 0;
boolean hasHigh = severityCounts.getOrDefault(FindingSeverity.HIGH, 0) > 0;
if (hasCritical) {
System.out.println("❌ FAIL: Found CRITICAL vulnerabilities");
return false;
}
if (failOnHighSeverity && hasHigh) {
System.out.println("❌ FAIL: Found HIGH vulnerabilities");
return false;
}
System.out.println("✅ PASS: No policy-violating vulnerabilities found");
return true;
} catch (EcrException e) {
System.err.println("Error evaluating scan results: " + e.awsErrorDetails().errorMessage());
return false;
}
}
}
Integrating ECR Scanning into CI/CD Pipelines
Here's a practical example of integrating ECR scanning into a Java-based CI/CD process using the AWS SDK.
Complete CI/CD Security Gate Example
import software.amazon.awssdk.services.ecr.EcrClient;
import software.amazon.awssdk.services.ecr.model.*;
public class CICDSecurityGate {
private final EcrClient ecrClient;
private final String repositoryName;
private final String imageTag;
public CICDSecurityGate(EcrClient ecrClient, String repositoryName, String imageTag) {
this.ecrClient = ecrClient;
this.repositoryName = repositoryName;
this.imageTag = imageTag;
}
public boolean executeSecurityScan() {
try {
ECRScanManager scanManager = new ECRScanManager();
ECRScanEvaluator evaluator = new ECRScanEvaluator(ecrClient);
System.out.println("Starting ECR image scan...");
scanManager.startImageScan(repositoryName, imageTag);
System.out.println("Waiting for scan completion (max 10 minutes)...");
boolean scanCompleted = evaluator.waitForScanCompletion(repositoryName, imageTag, 10);
if (!scanCompleted) {
System.out.println("❌ Scan did not complete within timeout period");
return false;
}
System.out.println("Evaluating scan against security policy...");
// Policy: Fail on CRITICAL or HIGH severity vulnerabilities
return evaluator.passesSecurityPolicy(repositoryName, imageTag, true);
} catch (Exception e) {
System.err.println("Security scan failed with error: " + e.getMessage());
return false;
}
}
public static void main(String[] args) {
if (args.length != 2) {
System.out.println("Usage: java CICDSecurityGate <repository-name> <image-tag>");
System.exit(1);
}
String repositoryName = args[0];
String imageTag = args[1];
EcrClient ecrClient = EcrClient.builder()
.region(Region.US_EAST_1)
.credentialsProvider(DefaultCredentialsProvider.create())
.build();
CICDSecurityGate securityGate = new CICDSecurityGate(ecrClient, repositoryName, imageTag);
boolean scanPassed = securityGate.executeSecurityScan();
System.exit(scanPassed ? 0 : 1); // Exit code for CI/CD pipeline
}
}
Maven Execution in CI Pipeline
You can execute this as part of your Maven build:
# Build your application
mvn clean compile
# Run the security gate
mvn exec:java -Dexec.mainClass="com.yourcompany.CICDSecurityGate" \
-Dexec.args="my-app-repository latest"
Best Practices for ECR Image Scanning in Java
- Implement Quality Gates: Fail builds automatically when critical vulnerabilities are detected. Use the exit code from your Java application to control CI/CD pipeline progression.
- Leverage IAM Roles Securely: Use IAM roles instead of long-term credentials when running in AWS environments (EC2, ECS, Lambda). For other environments, use temporary credentials.
- Handle Pagination: For images with many findings, implement pagination using the
nextTokenfrom the response. - Set Up Notifications: Combine with Amazon EventBridge to get notifications for scan findings and trigger your Java applications accordingly.
- Use Enhanced Scanning: For production workloads, consider enabling enhanced scanning for more comprehensive vulnerability detection.
- Regularly Update Base Images: The most effective way to reduce vulnerabilities is to use updated base images. Integrate this scanning with your base image update process.
Conclusion
Integrating ECR Image Scanning into your Java applications and CI/CD pipelines provides a robust mechanism for enforcing container security policies. By leveraging the AWS SDK for Java, you can automate vulnerability detection, implement quality gates, and ensure that only secure container images progress through your deployment pipeline.
This programmatic approach transforms container security from a manual, periodic audit into an automated, continuous process that aligns with modern DevOps practices and helps maintain a strong security posture for your cloud-native applications.
Further Reading: Explore the AWS SDK for Java v2 documentation for additional ECR operations, and consider integrating with AWS Security Hub for centralized security findings management across your organization.