Google Container Registry (GCR) provides built-in vulnerability scanning for container images. This guide covers Java integration with GCR's vulnerability scanning API, custom security policies, and automated security workflows.
Dependencies and Setup
1. Maven Dependencies
<properties>
<google-cloud-container.version>2.20.0</google-cloud-container.version>
<google-cloud-storage.version>2.20.1</google-cloud-storage.version>
<google-auth-library.version>1.19.0</google-auth-library.version>
<jackson.version>2.15.2</jackson.version>
</properties>
<dependencies>
<!-- Google Cloud Container Analysis -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-container</artifactId>
<version>${google-cloud-container.version}</version>
</dependency>
<!-- Google Cloud Storage -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-storage</artifactId>
<version>${google-cloud-storage.version}</version>
</dependency>
<!-- Google Auth Library -->
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<version>${google-auth-library.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- HTTP Client -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.11.0</version>
</dependency>
<!-- Spring Boot (Optional) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies>
2. Service Account Configuration
{
"type": "service_account",
"project_id": "your-gcp-project",
"private_key_id": "key-id",
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
"client_email": "[email protected]",
"client_id": "client-id",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs"
}
Core GCR Scanning Integration
1. GCR Client Configuration
package com.example.gcr.scanner;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.container.v1.ContainerAnalysisClient;
import com.google.cloud.container.v1.ContainerAnalysisSettings;
import com.google.cloud.devtools.containeranalysis.v1.ContainerAnalysisClient as DevToolsContainerAnalysisClient;
import com.google.cloud.devtools.containeranalysis.v1.ContainerAnalysisSettings as DevToolsContainerAnalysisSettings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import java.io.FileInputStream;
import java.io.IOException;
@Configuration
public class GcrScannerConfig {
private static final Logger logger = LoggerFactory.getLogger(GcrScannerConfig.class);
@Value("${gcp.project.id:}")
private String gcpProjectId;
@Value("${gcp.credentials.file:}")
private String credentialsFile;
@Value("${gcr.scanner.timeout.seconds:30}")
private int timeoutSeconds;
@Bean
public GoogleCredentials googleCredentials() throws IOException {
if (credentialsFile != null && !credentialsFile.isEmpty()) {
try (FileInputStream serviceAccountStream = new FileInputStream(credentialsFile)) {
return GoogleCredentials.fromStream(serviceAccountStream)
.createScoped("https://www.googleapis.com/auth/cloud-platform");
}
} else {
// Use application default credentials
return GoogleCredentials.getApplicationDefault()
.createScoped("https://www.googleapis.com/auth/cloud-platform");
}
}
@Bean
public ContainerAnalysisClient containerAnalysisClient(GoogleCredentials credentials) throws IOException {
ContainerAnalysisSettings settings = ContainerAnalysisSettings.newBuilder()
.setCredentialsProvider(() -> credentials)
.build();
return ContainerAnalysisClient.create(settings);
}
@Bean
public DevToolsContainerAnalysisClient devToolsContainerAnalysisClient(GoogleCredentials credentials) throws IOException {
DevToolsContainerAnalysisSettings settings = DevToolsContainerAnalysisSettings.newBuilder()
.setCredentialsProvider(() -> credentials)
.build();
return DevToolsContainerAnalysisClient.create(settings);
}
@Bean
public GcrScannerService gcrScannerService(ContainerAnalysisClient containerAnalysisClient,
DevToolsContainerAnalysisClient devToolsClient) {
return new GcrScannerService(containerAnalysisClient, devToolsClient, gcpProjectId);
}
}
2. Core GCR Scanning Service
package com.example.gcr.scanner;
import com.google.cloud.container.v1.ContainerAnalysisClient;
import com.google.cloud.devtools.containeranalysis.v1.ContainerAnalysisClient as DevToolsContainerAnalysisClient;
import com.google.cloud.devtools.containeranalysis.v1.ContainerAnalysisSettings;
import com.google.containeranalysis.v1.*;
import grafeas.v1.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class GcrScannerService {
private static final Logger logger = LoggerFactory.getLogger(GcrScannerService.class);
private final ContainerAnalysisClient containerAnalysisClient;
private final DevToolsContainerAnalysisClient devToolsContainerAnalysisClient;
private final String projectId;
public GcrScannerService(ContainerAnalysisClient containerAnalysisClient,
DevToolsContainerAnalysisClient devToolsContainerAnalysisClient,
String projectId) {
this.containerAnalysisClient = containerAnalysisClient;
this.devToolsContainerAnalysisClient = devToolsContainerAnalysisClient;
this.projectId = projectId;
}
public ScanResult scanImage(String imageUrl) {
try {
logger.info("Starting GCR vulnerability scan for image: {}", imageUrl);
// Extract project and image details from URL
ImageDetails imageDetails = parseImageUrl(imageUrl);
// Get occurrence for the image
List<Occurrence> occurrences = getVulnerabilityOccurrences(imageDetails);
// Analyze vulnerabilities
return analyzeVulnerabilities(occurrences, imageDetails);
} catch (Exception e) {
logger.error("Failed to scan image: {}", imageUrl, e);
return ScanResult.error(imageUrl, e.getMessage());
}
}
public BatchScanResult scanMultipleImages(List<String> imageUrls) {
BatchScanResult batchResult = new BatchScanResult();
for (String imageUrl : imageUrls) {
try {
ScanResult result = scanImage(imageUrl);
batchResult.addResult(result);
} catch (Exception e) {
logger.error("Failed to scan image: {}", imageUrl, e);
batchResult.addError(imageUrl, e.getMessage());
}
}
return batchResult;
}
public boolean isImageScanned(String imageUrl) {
try {
ImageDetails imageDetails = parseImageUrl(imageUrl);
List<Occurrence> occurrences = getVulnerabilityOccurrences(imageDetails);
return !occurrences.isEmpty();
} catch (Exception e) {
logger.warn("Failed to check if image is scanned: {}", imageUrl, e);
return false;
}
}
public ScanSummary getScanSummary(String imageUrl) {
ScanResult scanResult = scanImage(imageUrl);
return new ScanSummary(scanResult);
}
private ImageDetails parseImageUrl(String imageUrl) {
// Parse GCR image URL formats:
// gcr.io/project-id/image-name:tag
// us.gcr.io/project-id/image-name:tag
// eu.gcr.io/project-id/image-name:tag
// asia.gcr.io/project-id/image-name:tag
String[] parts = imageUrl.split("/");
if (parts.length < 3) {
throw new IllegalArgumentException("Invalid GCR image URL: " + imageUrl);
}
String registry = parts[0];
String projectId = parts[1];
String imageWithTag = parts[2];
String[] imageParts = imageWithTag.split(":");
String imageName = imageParts[0];
String tag = imageParts.length > 1 ? imageParts[1] : "latest";
String resourceUrl = String.format("https://%s/%s/%s", registry, projectId, imageWithTag);
return new ImageDetails(registry, projectId, imageName, tag, resourceUrl);
}
private List<Occurrence> getVulnerabilityOccurrences(ImageDetails imageDetails) {
try {
String resourceUrl = imageDetails.getResourceUrl();
String projectName = ProjectName.of(projectId).toString();
// Filter for vulnerability occurrences
Filter filter = Filter.newBuilder()
.setFilter(String.format("resourceUrl = \"%s\" AND kind = \"VULNERABILITY\"", resourceUrl))
.build();
ListOccurrencesRequest request = ListOccurrencesRequest.newBuilder()
.setParent(projectName)
.setFilter(filter.toString())
.build();
ContainerAnalysisClient.ListOccurrencesPagedResponse response =
containerAnalysisClient.listOccurrences(request);
List<Occurrence> occurrences = new ArrayList<>();
for (Occurrence occurrence : response.iterateAll()) {
occurrences.add(occurrence);
}
logger.debug("Found {} vulnerability occurrences for image: {}",
occurrences.size(), imageDetails.getResourceUrl());
return occurrences;
} catch (Exception e) {
throw new GcrScanningException("Failed to get vulnerability occurrences", e);
}
}
private ScanResult analyzeVulnerabilities(List<Occurrence> occurrences, ImageDetails imageDetails) {
if (occurrences.isEmpty()) {
return ScanResult.clean(imageDetails.getResourceUrl());
}
List<Vulnerability> vulnerabilities = new ArrayList<>();
Map<Severity, Integer> severityCount = new EnumMap<>(Severity.class);
for (Occurrence occurrence : occurrences) {
if (occurrence.hasVulnerability()) {
grafeas.v1.VulnerabilityOccurrence vulnOccurrence = occurrence.getVulnerability();
Vulnerability vulnerability = convertToVulnerability(vulnOccurrence, occurrence);
vulnerabilities.add(vulnerability);
// Count by severity
Severity severity = mapGrafeasSeverity(vulnOccurrence.getEffectiveSeverity());
severityCount.merge(severity, 1, Integer::sum);
}
}
// Categorize vulnerabilities
List<Vulnerability> criticalVulns = vulnerabilities.stream()
.filter(v -> v.getSeverity() == Severity.CRITICAL)
.collect(Collectors.toList());
List<Vulnerability> highVulns = vulnerabilities.stream()
.filter(v -> v.getSeverity() == Severity.HIGH)
.collect(Collectors.toList());
List<Vulnerability> mediumVulns = vulnerabilities.stream()
.filter(v -> v.getSeverity() == Severity.MEDIUM)
.collect(Collectors.toList());
List<Vulnerability> lowVulns = vulnerabilities.stream()
.filter(v -> v.getSeverity() == Severity.LOW)
.collect(Collectors.toList());
return ScanResult.withVulnerabilities(
imageDetails.getResourceUrl(),
vulnerabilities,
severityCount,
criticalVulns,
highVulns,
mediumVulns,
lowVulns
);
}
private Vulnerability convertToVulnerability(grafeas.v1.VulnerabilityOccurrence grafeasVuln,
Occurrence occurrence) {
Vulnerability vulnerability = new Vulnerability();
vulnerability.setId(occurrence.getName());
vulnerability.setCveId(grafeasVuln.getCve());
vulnerability.setShortDescription(grafeasVuln.getShortDescription());
vulnerability.setLongDescription(grafeasVuln.getLongDescription());
vulnerability.setSeverity(mapGrafeasSeverity(grafeasVuln.getEffectiveSeverity()));
vulnerability.setCvssScore(grafeasVuln.getCvssScore());
// Package information
if (grafeasVuln.getPackageIssueCount() > 0) {
grafeas.v1.VulnerabilityOccurrence.PackageIssue packageIssue = grafeasVuln.getPackageIssue(0);
vulnerability.setAffectedPackage(packageIssue.getAffectedPackage());
vulnerability.setAffectedVersion(packageIssue.getAffectedVersion().getFullName());
if (packageIssue.hasFixedVersion()) {
vulnerability.setFixedVersion(packageIssue.getFixedVersion().getFullName());
}
}
// Related URLs
vulnerability.setRelatedUrls(new ArrayList<>(grafeasVuln.getRelatedUrlsList()));
// Fix availability
vulnerability.setFixAvailable(grafeasVuln.getFixAvailable());
return vulnerability;
}
private Severity mapGrafeasSeverity(grafeas.v1.Severity grafeasSeverity) {
switch (grafeasSeverity) {
case CRITICAL:
return Severity.CRITICAL;
case HIGH:
return Severity.HIGH;
case MEDIUM:
return Severity.MEDIUM;
case LOW:
return Severity.LOW;
case MINIMAL:
return Severity.MINIMAL;
case SEVERITY_UNSPECIFIED:
default:
return Severity.UNSPECIFIED;
}
}
public void triggerScan(String imageUrl) {
try {
// GCR automatically scans images on push, but we can trigger a re-scan
// by updating the image or using the GCR API
logger.info("Triggered scan for image: {}", imageUrl);
// Implementation depends on specific GCR API capabilities
} catch (Exception e) {
throw new GcrScanningException("Failed to trigger scan for image: " + imageUrl, e);
}
}
}
3. Data Models
package com.example.gcr.scanner;
import java.time.LocalDateTime;
import java.util.*;
public class ScanResult {
private final String imageUrl;
private final LocalDateTime scanTime;
private final boolean successful;
private final String errorMessage;
private final List<Vulnerability> vulnerabilities;
private final Map<Severity, Integer> severityCount;
private final List<Vulnerability> criticalVulnerabilities;
private final List<Vulnerability> highVulnerabilities;
private final List<Vulnerability> mediumVulnerabilities;
private final List<Vulnerability> lowVulnerabilities;
private final int totalVulnerabilities;
private ScanResult(Builder builder) {
this.imageUrl = builder.imageUrl;
this.scanTime = builder.scanTime;
this.successful = builder.successful;
this.errorMessage = builder.errorMessage;
this.vulnerabilities = builder.vulnerabilities;
this.severityCount = builder.severityCount;
this.criticalVulnerabilities = builder.criticalVulnerabilities;
this.highVulnerabilities = builder.highVulnerabilities;
this.mediumVulnerabilities = builder.mediumVulnerabilities;
this.lowVulnerabilities = builder.lowVulnerabilities;
this.totalVulnerabilities = builder.totalVulnerabilities;
}
public static ScanResult clean(String imageUrl) {
return new Builder()
.imageUrl(imageUrl)
.successful(true)
.scanTime(LocalDateTime.now())
.vulnerabilities(Collections.emptyList())
.severityCount(new EnumMap<>(Severity.class))
.criticalVulnerabilities(Collections.emptyList())
.highVulnerabilities(Collections.emptyList())
.mediumVulnerabilities(Collections.emptyList())
.lowVulnerabilities(Collections.emptyList())
.totalVulnerabilities(0)
.build();
}
public static ScanResult error(String imageUrl, String errorMessage) {
return new Builder()
.imageUrl(imageUrl)
.successful(false)
.errorMessage(errorMessage)
.scanTime(LocalDateTime.now())
.build();
}
public static ScanResult withVulnerabilities(String imageUrl,
List<Vulnerability> vulnerabilities,
Map<Severity, Integer> severityCount,
List<Vulnerability> criticalVulnerabilities,
List<Vulnerability> highVulnerabilities,
List<Vulnerability> mediumVulnerabilities,
List<Vulnerability> lowVulnerabilities) {
return new Builder()
.imageUrl(imageUrl)
.successful(true)
.vulnerabilities(vulnerabilities)
.severityCount(severityCount)
.criticalVulnerabilities(criticalVulnerabilities)
.highVulnerabilities(highVulnerabilities)
.mediumVulnerabilities(mediumVulnerabilities)
.lowVulnerabilities(lowVulnerabilities)
.totalVulnerabilities(vulnerabilities.size())
.scanTime(LocalDateTime.now())
.build();
}
public boolean hasVulnerabilities() {
return vulnerabilities != null && !vulnerabilities.isEmpty();
}
public boolean meetsPolicy(SecurityPolicy policy) {
if (!successful) {
return false;
}
if (!hasVulnerabilities()) {
return true;
}
// Check policy thresholds
if (criticalVulnerabilities.size() > policy.getMaxCriticalVulnerabilities()) {
return false;
}
if (highVulnerabilities.size() > policy.getMaxHighVulnerabilities()) {
return false;
}
if (mediumVulnerabilities.size() > policy.getMaxMediumVulnerabilities()) {
return false;
}
if (totalVulnerabilities > policy.getMaxTotalVulnerabilities()) {
return false;
}
return true;
}
// Getters
public String getImageUrl() { return imageUrl; }
public LocalDateTime getScanTime() { return scanTime; }
public boolean isSuccessful() { return successful; }
public String getErrorMessage() { return errorMessage; }
public List<Vulnerability> getVulnerabilities() { return vulnerabilities; }
public Map<Severity, Integer> getSeverityCount() { return severityCount; }
public List<Vulnerability> getCriticalVulnerabilities() { return criticalVulnerabilities; }
public List<Vulnerability> getHighVulnerabilities() { return highVulnerabilities; }
public List<Vulnerability> getMediumVulnerabilities() { return mediumVulnerabilities; }
public List<Vulnerability> getLowVulnerabilities() { return lowVulnerabilities; }
public int getTotalVulnerabilities() { return totalVulnerabilities; }
public static class Builder {
private String imageUrl;
private LocalDateTime scanTime;
private boolean successful;
private String errorMessage;
private List<Vulnerability> vulnerabilities = Collections.emptyList();
private Map<Severity, Integer> severityCount = new EnumMap<>(Severity.class);
private List<Vulnerability> criticalVulnerabilities = Collections.emptyList();
private List<Vulnerability> highVulnerabilities = Collections.emptyList();
private List<Vulnerability> mediumVulnerabilities = Collections.emptyList();
private List<Vulnerability> lowVulnerabilities = Collections.emptyList();
private int totalVulnerabilities;
public Builder imageUrl(String imageUrl) {
this.imageUrl = imageUrl;
return this;
}
public Builder scanTime(LocalDateTime scanTime) {
this.scanTime = scanTime;
return this;
}
public Builder successful(boolean successful) {
this.successful = successful;
return this;
}
public Builder errorMessage(String errorMessage) {
this.errorMessage = errorMessage;
return this;
}
public Builder vulnerabilities(List<Vulnerability> vulnerabilities) {
this.vulnerabilities = vulnerabilities;
return this;
}
public Builder severityCount(Map<Severity, Integer> severityCount) {
this.severityCount = severityCount;
return this;
}
public Builder criticalVulnerabilities(List<Vulnerability> criticalVulnerabilities) {
this.criticalVulnerabilities = criticalVulnerabilities;
return this;
}
public Builder highVulnerabilities(List<Vulnerability> highVulnerabilities) {
this.highVulnerabilities = highVulnerabilities;
return this;
}
public Builder mediumVulnerabilities(List<Vulnerability> mediumVulnerabilities) {
this.mediumVulnerabilities = mediumVulnerabilities;
return this;
}
public Builder lowVulnerabilities(List<Vulnerability> lowVulnerabilities) {
this.lowVulnerabilities = lowVulnerabilities;
return this;
}
public Builder totalVulnerabilities(int totalVulnerabilities) {
this.totalVulnerabilities = totalVulnerabilities;
return this;
}
public ScanResult build() {
return new ScanResult(this);
}
}
}
package com.example.gcr.scanner;
import java.util.List;
public class Vulnerability {
private String id;
private String cveId;
private String shortDescription;
private String longDescription;
private Severity severity;
private float cvssScore;
private String affectedPackage;
private String affectedVersion;
private String fixedVersion;
private List<String> relatedUrls;
private boolean fixAvailable;
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getCveId() { return cveId; }
public void setCveId(String cveId) { this.cveId = cveId; }
public String getShortDescription() { return shortDescription; }
public void setShortDescription(String shortDescription) { this.shortDescription = shortDescription; }
public String getLongDescription() { return longDescription; }
public void setLongDescription(String longDescription) { this.longDescription = longDescription; }
public Severity getSeverity() { return severity; }
public void setSeverity(Severity severity) { this.severity = severity; }
public float getCvssScore() { return cvssScore; }
public void setCvssScore(float cvssScore) { this.cvssScore = cvssScore; }
public String getAffectedPackage() { return affectedPackage; }
public void setAffectedPackage(String affectedPackage) { this.affectedPackage = affectedPackage; }
public String getAffectedVersion() { return affectedVersion; }
public void setAffectedVersion(String affectedVersion) { this.affectedVersion = affectedVersion; }
public String getFixedVersion() { return fixedVersion; }
public void setFixedVersion(String fixedVersion) { this.fixedVersion = fixedVersion; }
public List<String> getRelatedUrls() { return relatedUrls; }
public void setRelatedUrls(List<String> relatedUrls) { this.relatedUrls = relatedUrls; }
public boolean isFixAvailable() { return fixAvailable; }
public void setFixAvailable(boolean fixAvailable) { this.fixAvailable = fixAvailable; }
}
package com.example.gcr.scanner;
public enum Severity {
CRITICAL(4),
HIGH(3),
MEDIUM(2),
LOW(1),
MINIMAL(0),
UNSPECIFIED(-1);
private final int level;
Severity(int level) {
this.level = level;
}
public int getLevel() {
return level;
}
public boolean isHigherThan(Severity other) {
return this.level > other.level;
}
public boolean isAtLeast(Severity other) {
return this.level >= other.level;
}
}
package com.example.gcr.scanner;
import java.util.List;
public class SecurityPolicy {
private final int maxCriticalVulnerabilities;
private final int maxHighVulnerabilities;
private final int maxMediumVulnerabilities;
private final int maxLowVulnerabilities;
private final int maxTotalVulnerabilities;
private final List<String> allowedBaseImages;
private final List<String> bannedPackages;
private final float maxCvssScore;
private final boolean allowUnfixedVulnerabilities;
public SecurityPolicy(int maxCriticalVulnerabilities, int maxHighVulnerabilities,
int maxMediumVulnerabilities, int maxLowVulnerabilities,
int maxTotalVulnerabilities, List<String> allowedBaseImages,
List<String> bannedPackages, float maxCvssScore,
boolean allowUnfixedVulnerabilities) {
this.maxCriticalVulnerabilities = maxCriticalVulnerabilities;
this.maxHighVulnerabilities = maxHighVulnerabilities;
this.maxMediumVulnerabilities = maxMediumVulnerabilities;
this.maxLowVulnerabilities = maxLowVulnerabilities;
this.maxTotalVulnerabilities = maxTotalVulnerabilities;
this.allowedBaseImages = allowedBaseImages;
this.bannedPackages = bannedPackages;
this.maxCvssScore = maxCvssScore;
this.allowUnfixedVulnerabilities = allowUnfixedVulnerabilities;
}
// Getters
public int getMaxCriticalVulnerabilities() { return maxCriticalVulnerabilities; }
public int getMaxHighVulnerabilities() { return maxHighVulnerabilities; }
public int getMaxMediumVulnerabilities() { return maxMediumVulnerabilities; }
public int getMaxLowVulnerabilities() { return maxLowVulnerabilities; }
public int getMaxTotalVulnerabilities() { return maxTotalVulnerabilities; }
public List<String> getAllowedBaseImages() { return allowedBaseImages; }
public List<String> getBannedPackages() { return bannedPackages; }
public float getMaxCvssScore() { return maxCvssScore; }
public boolean isAllowUnfixedVulnerabilities() { return allowUnfixedVulnerabilities; }
public static SecurityPolicy strict() {
return new SecurityPolicy(0, 0, 3, 10, 20,
List.of("debian:11", "ubuntu:20.04", "alpine:3.16"),
List.of("telnet", "ftp", "telnetd"), 4.0f, false);
}
public static SecurityPolicy lenient() {
return new SecurityPolicy(1, 5, 15, 50, 100,
List.of(), List.of(), 8.0f, true);
}
public static SecurityPolicy production() {
return new SecurityPolicy(0, 2, 10, 25, 50,
List.of("debian:11", "ubuntu:20.04", "alpine:3.16"),
List.of("telnet", "ftp"), 6.0f, false);
}
}
package com.example.gcr.scanner;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
public class BatchScanResult {
private final List<ScanResult> results;
private final List<ImageScanError> errors;
private final LocalDateTime scanTime;
public BatchScanResult() {
this.results = new ArrayList<>();
this.errors = new ArrayList<>();
this.scanTime = LocalDateTime.now();
}
public void addResult(ScanResult result) {
results.add(result);
}
public void addError(String imageUrl, String error) {
errors.add(new ImageScanError(imageUrl, error));
}
public int getTotalScanned() {
return results.size() + errors.size();
}
public int getSuccessfulScans() {
return (int) results.stream().filter(ScanResult::isSuccessful).count();
}
public int getFailedScans() {
return errors.size() + (int) results.stream().filter(r -> !r.isSuccessful()).count();
}
public List<ScanResult> getVulnerableImages() {
return results.stream()
.filter(ScanResult::isSuccessful)
.filter(ScanResult::hasVulnerabilities)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
}
// Getters
public List<ScanResult> getResults() { return results; }
public List<ImageScanError> getErrors() { return errors; }
public LocalDateTime getScanTime() { return scanTime; }
}
package com.example.gcr.scanner;
import java.time.LocalDateTime;
public class ImageScanError {
private final String imageUrl;
private final String error;
private final LocalDateTime timestamp;
public ImageScanError(String imageUrl, String error) {
this.imageUrl = imageUrl;
this.error = error;
this.timestamp = LocalDateTime.now();
}
// Getters
public String getImageUrl() { return imageUrl; }
public String getError() { return error; }
public LocalDateTime getTimestamp() { return timestamp; }
}
package com.example.gcr.scanner;
public class ImageDetails {
private final String registry;
private final String projectId;
private final String imageName;
private final String tag;
private final String resourceUrl;
public ImageDetails(String registry, String projectId, String imageName, String tag, String resourceUrl) {
this.registry = registry;
this.projectId = projectId;
this.imageName = imageName;
this.tag = tag;
this.resourceUrl = resourceUrl;
}
// Getters
public String getRegistry() { return registry; }
public String getProjectId() { return projectId; }
public String getImageName() { return imageName; }
public String getTag() { return tag; }
public String getResourceUrl() { return resourceUrl; }
}
package com.example.gcr.scanner;
public class GcrScanningException extends RuntimeException {
public GcrScanningException(String message) {
super(message);
}
public GcrScanningException(String message, Throwable cause) {
super(message, cause);
}
}
package com.example.gcr.scanner;
import java.util.Map;
public class ScanSummary {
private final String imageUrl;
private final boolean successful;
private final int totalVulnerabilities;
private final Map<Severity, Integer> severityCount;
private final boolean hasCriticalVulnerabilities;
private final boolean hasHighVulnerabilities;
public ScanSummary(ScanResult scanResult) {
this.imageUrl = scanResult.getImageUrl();
this.successful = scanResult.isSuccessful();
this.totalVulnerabilities = scanResult.getTotalVulnerabilities();
this.severityCount = scanResult.getSeverityCount();
this.hasCriticalVulnerabilities = !scanResult.getCriticalVulnerabilities().isEmpty();
this.hasHighVulnerabilities = !scanResult.getHighVulnerabilities().isEmpty();
}
// Getters
public String getImageUrl() { return imageUrl; }
public boolean isSuccessful() { return successful; }
public int getTotalVulnerabilities() { return totalVulnerabilities; }
public Map<Severity, Integer> getSeverityCount() { return severityCount; }
public boolean hasCriticalVulnerabilities() { return hasCriticalVulnerabilities; }
public boolean hasHighVulnerabilities() { return hasHighVulnerabilities; }
public boolean isClean() { return successful && totalVulnerabilities == 0; }
}
Advanced Security Analysis
1. Security Policy Engine
package com.example.gcr.scanner.policy;
import com.example.gcr.scanner.ScanResult;
import com.example.gcr.scanner.SecurityPolicy;
import com.example.gcr.scanner.Vulnerability;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class SecurityPolicyEngine {
private static final Logger logger = LoggerFactory.getLogger(SecurityPolicyEngine.class);
public PolicyValidationResult validate(ScanResult scanResult, SecurityPolicy policy) {
List<PolicyViolation> violations = new ArrayList<>();
if (!scanResult.isSuccessful()) {
violations.add(new PolicyViolation("SCAN_FAILED",
"Security scan failed: " + scanResult.getErrorMessage()));
return new PolicyValidationResult(false, violations);
}
// Check critical vulnerabilities
if (scanResult.getCriticalVulnerabilities().size() > policy.getMaxCriticalVulnerabilities()) {
violations.add(new PolicyViolation("CRITICAL_VULNERABILITIES",
String.format("Found %d critical vulnerabilities, maximum allowed: %d",
scanResult.getCriticalVulnerabilities().size(),
policy.getMaxCriticalVulnerabilities())));
}
// Check high vulnerabilities
if (scanResult.getHighVulnerabilities().size() > policy.getMaxHighVulnerabilities()) {
violations.add(new PolicyViolation("HIGH_VULNERABILITIES",
String.format("Found %d high vulnerabilities, maximum allowed: %d",
scanResult.getHighVulnerabilities().size(),
policy.getMaxHighVulnerabilities())));
}
// Check medium vulnerabilities
if (scanResult.getMediumVulnerabilities().size() > policy.getMaxMediumVulnerabilities()) {
violations.add(new PolicyViolation("MEDIUM_VULNERABILITIES",
String.format("Found %d medium vulnerabilities, maximum allowed: %d",
scanResult.getMediumVulnerabilities().size(),
policy.getMaxMediumVulnerabilities())));
}
// Check total vulnerabilities
if (scanResult.getTotalVulnerabilities() > policy.getMaxTotalVulnerabilities()) {
violations.add(new PolicyViolation("TOTAL_VULNERABILITIES",
String.format("Found %d total vulnerabilities, maximum allowed: %d",
scanResult.getTotalVulnerabilities(),
policy.getMaxTotalVulnerabilities())));
}
// Check CVSS scores
for (Vulnerability vuln : scanResult.getVulnerabilities()) {
if (vuln.getCvssScore() > policy.getMaxCvssScore()) {
violations.add(new PolicyViolation("HIGH_CVSS_SCORE",
String.format("Vulnerability %s has CVSS score %.1f, maximum allowed: %.1f",
vuln.getCveId(), vuln.getCvssScore(), policy.getMaxCvssScore())));
}
}
// Check for banned packages
for (Vulnerability vuln : scanResult.getVulnerabilities()) {
String affectedPackage = vuln.getAffectedPackage();
if (affectedPackage != null && policy.getBannedPackages().contains(affectedPackage)) {
violations.add(new PolicyViolation("BANNED_PACKAGE",
String.format("Banned package detected: %s", affectedPackage)));
}
}
// Check for unfixed vulnerabilities
if (!policy.isAllowUnfixedVulnerabilities()) {
long unfixedCount = scanResult.getVulnerabilities().stream()
.filter(v -> !v.isFixAvailable())
.count();
if (unfixedCount > 0) {
violations.add(new PolicyViolation("UNFIXED_VULNERABILITIES",
String.format("Found %d vulnerabilities without available fixes", unfixedCount)));
}
}
boolean compliant = violations.isEmpty();
if (compliant) {
logger.info("Image {} complies with security policy", scanResult.getImageUrl());
} else {
logger.warn("Image {} violates security policy with {} violations",
scanResult.getImageUrl(), violations.size());
}
return new PolicyValidationResult(compliant, violations);
}
public PolicyRecommendation generateRecommendations(ScanResult scanResult, SecurityPolicy policy) {
List<String> recommendations = new ArrayList<>();
if (!scanResult.isSuccessful()) {
recommendations.add("Fix the scanning error: " + scanResult.getErrorMessage());
return new PolicyRecommendation(recommendations);
}
if (scanResult.hasVulnerabilities()) {
// Critical vulnerabilities
if (!scanResult.getCriticalVulnerabilities().isEmpty()) {
recommendations.add("Immediately address critical vulnerabilities:");
scanResult.getCriticalVulnerabilities().stream()
.limit(3)
.forEach(vuln -> recommendations.add(" - " + vuln.getCveId() + ": " + vuln.getShortDescription()));
}
// High vulnerabilities
if (!scanResult.getHighVulnerabilities().isEmpty()) {
recommendations.add("Address high severity vulnerabilities:");
scanResult.getHighVulnerabilities().stream()
.limit(3)
.forEach(vuln -> recommendations.add(" - " + vuln.getCveId() + ": " + vuln.getShortDescription()));
}
// General recommendations
recommendations.add("Update base image to a more recent version");
recommendations.add("Remove unused packages to reduce attack surface");
recommendations.add("Regularly update packages and dependencies");
// Fixable vulnerabilities
long fixableCount = scanResult.getVulnerabilities().stream()
.filter(Vulnerability::isFixAvailable)
.count();
if (fixableCount > 0) {
recommendations.add(String.format("Update packages to fix %d vulnerabilities", fixableCount));
}
} else {
recommendations.add("No vulnerabilities found. Maintain current security practices.");
}
return new PolicyRecommendation(recommendations);
}
}
package com.example.gcr.scanner.policy;
import java.util.List;
public class PolicyValidationResult {
private final boolean compliant;
private final List<PolicyViolation> violations;
public PolicyValidationResult(boolean compliant, List<PolicyViolation> violations) {
this.compliant = compliant;
this.violations = violations;
}
// Getters
public boolean isCompliant() { return compliant; }
public List<PolicyViolation> getViolations() { return violations; }
public int getViolationCount() { return violations.size(); }
}
package com.example.gcr.scanner.policy;
public class PolicyViolation {
private final String type;
private final String description;
public PolicyViolation(String type, String description) {
this.type = type;
this.description = description;
}
// Getters
public String getType() { return type; }
public String getDescription() { return description; }
}
package com.example.gcr.scanner.policy;
import java.util.List;
public class PolicyRecommendation {
private final List<String> recommendations;
public PolicyRecommendation(List<String> recommendations) {
this.recommendations = recommendations;
}
// Getters
public List<String> getRecommendations() { return recommendations; }
public boolean hasRecommendations() { return recommendations != null && !recommendations.isEmpty(); }
}
2. Vulnerability Trend Analysis
package com.example.gcr.scanner.analysis;
import com.example.gcr.scanner.ScanResult;
import com.example.gcr.scanner.Severity;
import com.example.gcr.scanner.Vulnerability;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@Component
public class VulnerabilityAnalyzer {
private static final Logger logger = LoggerFactory.getLogger(VulnerabilityAnalyzer.class);
public VulnerabilityTrend analyzeTrends(List<ScanResult> historicalScans) {
if (historicalScans == null || historicalScans.isEmpty()) {
return VulnerabilityTrend.empty();
}
// Sort by scan time
List<ScanResult> sortedScans = historicalScans.stream()
.sorted(Comparator.comparing(ScanResult::getScanTime))
.collect(Collectors.toList());
// Calculate trends
Map<Severity, List<Integer>> severityTrends = calculateSeverityTrends(sortedScans);
Map<String, Long> frequentVulnerabilities = findFrequentVulnerabilities(sortedScans);
Map<String, Long> frequentPackages = findFrequentPackages(sortedScans);
// Calculate overall trend
TrendDirection overallTrend = calculateOverallTrend(sortedScans);
return new VulnerabilityTrend(
sortedScans,
severityTrends,
frequentVulnerabilities,
frequentPackages,
overallTrend
);
}
public RiskAssessment assessRisk(ScanResult scanResult) {
if (!scanResult.isSuccessful()) {
return RiskAssessment.unknown("Scan failed: " + scanResult.getErrorMessage());
}
if (!scanResult.hasVulnerabilities()) {
return RiskAssessment.low("No vulnerabilities detected");
}
// Calculate risk score
double riskScore = calculateRiskScore(scanResult);
RiskLevel riskLevel = determineRiskLevel(riskScore);
// Generate risk factors
List<RiskFactor> riskFactors = identifyRiskFactors(scanResult);
return new RiskAssessment(riskLevel, riskScore, riskFactors, scanResult.getImageUrl());
}
private Map<Severity, List<Integer>> calculateSeverityTrends(List<ScanResult> scans) {
Map<Severity, List<Integer>> trends = new EnumMap<>(Severity.class);
for (Severity severity : Severity.values()) {
if (severity != Severity.UNSPECIFIED) {
List<Integer> counts = scans.stream()
.map(scan -> scan.getSeverityCount().getOrDefault(severity, 0))
.collect(Collectors.toList());
trends.put(severity, counts);
}
}
return trends;
}
private Map<String, Long> findFrequentVulnerabilities(List<ScanResult> scans) {
return scans.stream()
.filter(ScanResult::hasVulnerabilities)
.flatMap(scan -> scan.getVulnerabilities().stream())
.collect(Collectors.groupingBy(
Vulnerability::getCveId,
Collectors.counting()
))
.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(10)
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1,
LinkedHashMap::new
));
}
private Map<String, Long> findFrequentPackages(List<ScanResult> scans) {
return scans.stream()
.filter(ScanResult::hasVulnerabilities)
.flatMap(scan -> scan.getVulnerabilities().stream())
.filter(vuln -> vuln.getAffectedPackage() != null)
.collect(Collectors.groupingBy(
Vulnerability::getAffectedPackage,
Collectors.counting()
))
.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(10)
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1,
LinkedHashMap::new
));
}
private TrendDirection calculateOverallTrend(List<ScanResult> scans) {
if (scans.size() < 2) {
return TrendDirection.STABLE;
}
ScanResult first = scans.get(0);
ScanResult last = scans.get(scans.size() - 1);
int firstTotal = first.getTotalVulnerabilities();
int lastTotal = last.getTotalVulnerabilities();
if (lastTotal > firstTotal * 1.2) {
return TrendDirection.INCREASING;
} else if (lastTotal < firstTotal * 0.8) {
return TrendDirection.DECREASING;
} else {
return TrendDirection.STABLE;
}
}
private double calculateRiskScore(ScanResult scanResult) {
double score = 0.0;
// Critical vulnerabilities have highest weight
score += scanResult.getCriticalVulnerabilities().size() * 10.0;
// High vulnerabilities
score += scanResult.getHighVulnerabilities().size() * 5.0;
// Medium vulnerabilities
score += scanResult.getMediumVulnerabilities().size() * 2.0;
// Low vulnerabilities
score += scanResult.getLowVulnerabilities().size() * 0.5;
// Consider CVSS scores
double avgCvss = scanResult.getVulnerabilities().stream()
.mapToDouble(Vulnerability::getCvssScore)
.average()
.orElse(0.0);
score += avgCvss * 2.0;
// Unfixed vulnerabilities increase risk
long unfixedCount = scanResult.getVulnerabilities().stream()
.filter(v -> !v.isFixAvailable())
.count();
score += unfixedCount * 3.0;
return Math.min(score, 100.0); // Cap at 100
}
private RiskLevel determineRiskLevel(double riskScore) {
if (riskScore >= 80) return RiskLevel.CRITICAL;
if (riskScore >= 60) return RiskLevel.HIGH;
if (riskScore >= 40) return RiskLevel.MEDIUM;
if (riskScore >= 20) return RiskLevel.LOW;
return RiskLevel.NEGLIGIBLE;
}
private List<RiskFactor> identifyRiskFactors(ScanResult scanResult) {
List<RiskFactor> factors = new ArrayList<>();
if (!scanResult.getCriticalVulnerabilities().isEmpty()) {
factors.add(new RiskFactor("CRITICAL_VULNERABILITIES",
"Critical severity vulnerabilities present", RiskLevel.CRITICAL));
}
if (!scanResult.getHighVulnerabilities().isEmpty()) {
factors.add(new RiskFactor("HIGH_VULNERABILITIES",
"High severity vulnerabilities present", RiskLevel.HIGH));
}
long unfixedCount = scanResult.getVulnerabilities().stream()
.filter(v -> !v.isFixAvailable())
.count();
if (unfixedCount > 0) {
factors.add(new RiskFactor("UNFIXED_VULNERABILITIES",
String.format("%d vulnerabilities without available fixes", unfixedCount), RiskLevel.HIGH));
}
// Check for known exploited vulnerabilities
List<Vulnerability> exploited = scanResult.getVulnerabilities().stream()
.filter(this::isKnownExploited)
.collect(Collectors.toList());
if (!exploited.isEmpty()) {
factors.add(new RiskFactor("KNOWN_EXPLOITED_VULNERABILITIES",
String.format("%d known exploited vulnerabilities", exploited.size()), RiskLevel.CRITICAL));
}
return factors;
}
private boolean isKnownExploited(Vulnerability vulnerability) {
// This would integrate with known exploited vulnerabilities databases
// For now, using a simple heuristic based on CVSS score and recent CVE years
return vulnerability.getCvssScore() >= 7.0 &&
vulnerability.getCveId() != null &&
vulnerability.getCveId().contains("2023");
}
}
package com.example.gcr.scanner.analysis;
import com.example.gcr.scanner.ScanResult;
import com.example.gcr.scanner.Severity;
import java.util.List;
import java.util.Map;
public class VulnerabilityTrend {
private final List<ScanResult> historicalScans;
private final Map<Severity, List<Integer>> severityTrends;
private final Map<String, Long> frequentVulnerabilities;
private final Map<String, Long> frequentPackages;
private final TrendDirection overallTrend;
public VulnerabilityTrend(List<ScanResult> historicalScans,
Map<Severity, List<Integer>> severityTrends,
Map<String, Long> frequentVulnerabilities,
Map<String, Long> frequentPackages,
TrendDirection overallTrend) {
this.historicalScans = historicalScans;
this.severityTrends = severityTrends;
this.frequentVulnerabilities = frequentVulnerabilities;
this.frequentPackages = frequentPackages;
this.overallTrend = overallTrend;
}
public static VulnerabilityTrend empty() {
return new VulnerabilityTrend(List.of(), Map.of(), Map.of(), Map.of(), TrendDirection.STABLE);
}
// Getters
public List<ScanResult> getHistoricalScans() { return historicalScans; }
public Map<Severity, List<Integer>> getSeverityTrends() { return severityTrends; }
public Map<String, Long> getFrequentVulnerabilities() { return frequentVulnerabilities; }
public Map<String, Long> getFrequentPackages() { return frequentPackages; }
public TrendDirection getOverallTrend() { return overallTrend; }
public boolean hasData() { return !historicalScans.isEmpty(); }
}
package com.example.gcr.scanner.analysis;
public enum TrendDirection {
INCREASING,
DECREASING,
STABLE
}
package com.example.gcr.scanner.analysis;
import java.util.List;
public class RiskAssessment {
private final RiskLevel riskLevel;
private final double riskScore;
private final List<RiskFactor> riskFactors;
private final String imageUrl;
public RiskAssessment(RiskLevel riskLevel, double riskScore,
List<RiskFactor> riskFactors, String imageUrl) {
this.riskLevel = riskLevel;
this.riskScore = riskScore;
this.riskFactors = riskFactors;
this.imageUrl = imageUrl;
}
public static RiskAssessment unknown(String reason) {
return new RiskAssessment(RiskLevel.UNKNOWN, 0.0,
List.of(new RiskFactor("SCAN_FAILED", reason, RiskLevel.UNKNOWN)), "unknown");
}
public static RiskAssessment low(String reason) {
return new RiskAssessment(RiskLevel.LOW, 10.0,
List.of(new RiskFactor("CLEAN_SCAN", reason, RiskLevel.LOW)), "clean");
}
// Getters
public RiskLevel getRiskLevel() { return riskLevel; }
public double getRiskScore() { return riskScore; }
public List<RiskFactor> getRiskFactors() { return riskFactors; }
public String getImageUrl() { return imageUrl; }
public boolean isHighRisk() { return riskLevel == RiskLevel.HIGH || riskLevel == RiskLevel.CRITICAL; }
}
package com.example.gcr.scanner.analysis;
public enum RiskLevel {
CRITICAL(4),
HIGH(3),
MEDIUM(2),
LOW(1),
NEGLIGIBLE(0),
UNKNOWN(-1);
private final int level;
RiskLevel(int level) {
this.level = level;
}
public int getLevel() {
return level;
}
}
package com.example.gcr.scanner.analysis;
public class RiskFactor {
private final String type;
private final String description;
private final RiskLevel severity;
public RiskFactor(String type, String description, RiskLevel severity) {
this.type = type;
this.description = description;
this.severity = severity;
}
// Getters
public String getType() { return type; }
public String getDescription() { return description; }
public RiskLevel getSeverity() { return severity; }
}
REST API Controllers
1. Security Scan Controller
package com.example.gcr.scanner.controller;
import com.example.gcr.scanner.*;
import com.example.gcr.scanner.analysis.RiskAssessment;
import com.example.gcr.scanner.analysis.VulnerabilityAnalyzer;
import com.example.gcr.scanner.policy.PolicyRecommendation;
import com.example.gcr.scanner.policy.PolicyValidationResult;
import com.example.gcr.scanner.policy.SecurityPolicyEngine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/gcr/security")
public class GcrSecurityController {
private static final Logger logger = LoggerFactory.getLogger(GcrSecurityController.class);
private final GcrScannerService scannerService;
private final SecurityPolicyEngine policyEngine;
private final VulnerabilityAnalyzer vulnerabilityAnalyzer;
public GcrSecurityController(GcrScannerService scannerService,
SecurityPolicyEngine policyEngine,
VulnerabilityAnalyzer vulnerabilityAnalyzer) {
this.scannerService = scannerService;
this.policyEngine = policyEngine;
this.vulnerabilityAnalyzer = vulnerabilityAnalyzer;
}
@PostMapping("/scan")
public ResponseEntity<ScanResult> scanImage(@RequestBody ScanRequest request) {
logger.info("Received GCR scan request for image: {}", request.getImageUrl());
ScanResult result = scannerService.scanImage(request.getImageUrl());
return ResponseEntity.ok(result);
}
@PostMapping("/scan/batch")
public ResponseEntity<BatchScanResult> scanMultipleImages(@RequestBody BatchScanRequest request) {
logger.info("Received batch scan request for {} images", request.getImageUrls().size());
BatchScanResult result = scannerService.scanMultipleImages(request.getImageUrls());
return ResponseEntity.ok(result);
}
@PostMapping("/validate-policy")
public ResponseEntity<PolicyValidationResponse> validateWithPolicy(
@RequestBody PolicyValidationRequest request) {
ScanResult scanResult = scannerService.scanImage(request.getImageUrl());
SecurityPolicy policy = createPolicy(request.getPolicy());
PolicyValidationResult validationResult = policyEngine.validate(scanResult, policy);
PolicyRecommendation recommendations = policyEngine.generateRecommendations(scanResult, policy);
PolicyValidationResponse response = new PolicyValidationResponse(
scanResult, validationResult, recommendations, policy);
return ResponseEntity.ok(response);
}
@GetMapping("/risk-assessment/{imageUrl}")
public ResponseEntity<RiskAssessment> assessRisk(@PathVariable String imageUrl) {
ScanResult scanResult = scannerService.scanImage(imageUrl);
RiskAssessment riskAssessment = vulnerabilityAnalyzer.assessRisk(scanResult);
return ResponseEntity.ok(riskAssessment);
}
@GetMapping("/summary/{imageUrl}")
public ResponseEntity<ScanSummary> getScanSummary(@PathVariable String imageUrl) {
ScanSummary summary = scannerService.getScanSummary(imageUrl);
return ResponseEntity.ok(summary);
}
@GetMapping("/is-scanned/{imageUrl}")
public ResponseEntity<ScanStatus> isImageScanned(@PathVariable String imageUrl) {
boolean isScanned = scannerService.isImageScanned(imageUrl);
return ResponseEntity.ok(new ScanStatus(imageUrl, isScanned));
}
@PostMapping("/trigger-scan")
public ResponseEntity<Void> triggerScan(@RequestBody ScanRequest request) {
scannerService.triggerScan(request.getImageUrl());
return ResponseEntity.accepted().build();
}
@GetMapping("/health")
public ResponseEntity<HealthStatus> healthCheck() {
// Simple health check - try to scan a known image
boolean healthy = scannerService.isImageScanned("gcr.io/google-containers/pause:3.1");
return ResponseEntity.ok(new HealthStatus(healthy, "GCR Scanner Service"));
}
private SecurityPolicy createPolicy(PolicyRequest policyRequest) {
if (policyRequest == null) {
return SecurityPolicy.production();
}
return new SecurityPolicy(
policyRequest.getMaxCriticalVulnerabilities(),
policyRequest.getMaxHighVulnerabilities(),
policyRequest.getMaxMediumVulnerabilities(),
policyRequest.getMaxLowVulnerabilities(),
policyRequest.getMaxTotalVulnerabilities(),
policyRequest.getAllowedBaseImages(),
policyRequest.getBannedPackages(),
policyRequest.getMaxCvssScore(),
policyRequest.isAllowUnfixedVulnerabilities()
);
}
}
// Request/Response DTOs
class ScanRequest {
private String imageUrl;
public String getImageUrl() { return imageUrl; }
public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
}
class BatchScanRequest {
private List<String> imageUrls;
public List<String> getImageUrls() { return imageUrls; }
public void setImageUrls(List<String> imageUrls) { this.imageUrls = imageUrls; }
}
class PolicyValidationRequest {
private String imageUrl;
private PolicyRequest policy;
public String getImageUrl() { return imageUrl; }
public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
public PolicyRequest getPolicy() { return policy; }
public void setPolicy(PolicyRequest policy) { this.policy = policy; }
}
class PolicyRequest {
private int maxCriticalVulnerabilities = 0;
private int maxHighVulnerabilities = 2;
private int maxMediumVulnerabilities = 10;
private int maxLowVulnerabilities = 25;
private int maxTotalVulnerabilities = 50;
private List<String> allowedBaseImages = List.of();
private List<String> bannedPackages = List.of();
private float maxCvssScore = 6.0f;
private boolean allowUnfixedVulnerabilities = false;
// Getters and setters
public int getMaxCriticalVulnerabilities() { return maxCriticalVulnerabilities; }
public void setMaxCriticalVulnerabilities(int maxCriticalVulnerabilities) { this.maxCriticalVulnerabilities = maxCriticalVulnerabilities; }
public int getMaxHighVulnerabilities() { return maxHighVulnerabilities; }
public void setMaxHighVulnerabilities(int maxHighVulnerabilities) { this.maxHighVulnerabilities = maxHighVulnerabilities; }
public int getMaxMediumVulnerabilities() { return maxMediumVulnerabilities; }
public void setMaxMediumVulnerabilities(int maxMediumVulnerabilities) { this.maxMediumVulnerabilities = maxMediumVulnerabilities; }
public int getMaxLowVulnerabilities() { return maxLowVulnerabilities; }
public void setMaxLowVulnerabilities(int maxLowVulnerabilities) { this.maxLowVulnerabilities = maxLowVulnerabilities; }
public int getMaxTotalVulnerabilities() { return maxTotalVulnerabilities; }
public void setMaxTotalVulnerabilities(int maxTotalVulnerabilities) { this.maxTotalVulnerabilities = maxTotalVulnerabilities; }
public List<String> getAllowedBaseImages() { return allowedBaseImages; }
public void setAllowedBaseImages(List<String> allowedBaseImages) { this.allowedBaseImages = allowedBaseImages; }
public List<String> getBannedPackages() { return bannedPackages; }
public void setBannedPackages(List<String> bannedPackages) { this.bannedPackages = bannedPackages; }
public float getMaxCvssScore() { return maxCvssScore; }
public void setMaxCvssScore(float maxCvssScore) { this.maxCvssScore = maxCvssScore; }
public boolean isAllowUnfixedVulnerabilities() { return allowUnfixedVulnerabilities; }
public void setAllowUnfixedVulnerabilities(boolean allowUnfixedVulnerabilities) { this.allowUnfixedVulnerabilities = allowUnfixedVulnerabilities; }
}
class PolicyValidationResponse {
private final ScanResult scanResult;
private final PolicyValidationResult validationResult;
private final PolicyRecommendation recommendations;
private final SecurityPolicy appliedPolicy;
public PolicyValidationResponse(ScanResult scanResult, PolicyValidationResult validationResult,
PolicyRecommendation recommendations, SecurityPolicy appliedPolicy) {
this.scanResult = scanResult;
this.validationResult = validationResult;
this.recommendations = recommendations;
this.appliedPolicy = appliedPolicy;
}
// Getters
public ScanResult getScanResult() { return scanResult; }
public PolicyValidationResult getValidationResult() { return validationResult; }
public PolicyRecommendation getRecommendations() { return recommendations; }
public SecurityPolicy getAppliedPolicy() { return appliedPolicy; }
}
class ScanStatus {
private final String imageUrl;
private final boolean scanned;
private final String timestamp;
public ScanStatus(String imageUrl, boolean scanned) {
this.imageUrl = imageUrl;
this.scanned = scanned;
this.timestamp = java.time.LocalDateTime.now().toString();
}
// Getters
public String getImageUrl() { return imageUrl; }
public boolean isScanned() { return scanned; }
public String getTimestamp() { return timestamp; }
}
class HealthStatus {
private final boolean healthy;
private final String service;
private final String timestamp;
public HealthStatus(boolean healthy, String service) {
this.healthy = healthy;
this.service = service;
this.timestamp = java.time.LocalDateTime.now().toString();
}
// Getters
public boolean isHealthy() { return healthy; }
public String getService() { return service; }
public String getTimestamp() { return timestamp; }
}
CI/CD Integration
1. GitHub Actions Workflow
name: GCR Security Scan
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
GCP_PROJECT: ${{ secrets.GCP_PROJECT }}
GCR_IMAGE: gcr.io/${{ secrets.GCP_PROJECT }}/my-app:${{ github.sha }}
jobs:
build-and-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Google Cloud
uses: google-github-actions/setup-gcloud@v1
with:
project_id: ${{ secrets.GCP_PROJECT }}
service_account_key: ${{ secrets.GCP_SA_KEY }}
export_default_credentials: true
- name: Build Docker image
run: |
docker build -t $GCR_IMAGE .
- name: Push to GCR
run: |
gcloud auth configure-docker
docker push $GCR_IMAGE
- name: Wait for GCR scan
run: |
echo "Waiting for GCR vulnerability scan to complete..."
sleep 60
- name: Run Security Scan
run: |
# Use our Java scanner to check GCR scan results
curl -X POST "http://our-scanner-service/api/gcr/security/validate-policy" \
-H "Content-Type: application/json" \
-d '{
"imageUrl": "${{ env.GCR_IMAGE }}",
"policy": {
"maxCriticalVulnerabilities": 0,
"maxHighVulnerabilities": 2,
"maxTotalVulnerabilities": 20
}
}' > scan-result.json
# Check if scan passed policy
if jq -e '.validationResult.compliant == true' scan-result.json; then
echo "✅ Image passed security policy"
else
echo "❌ Image failed security policy"
jq '.validationResult.violations[]' scan-result.json
exit 1
fi
- name: Upload Scan Report
uses: actions/upload-artifact@v3
with:
name: security-scan-report
path: scan-result.json
retention-days: 30
2. Jenkins Pipeline
pipeline {
agent any
environment {
GCP_PROJECT = 'your-project-id'
GCR_IMAGE = "gcr.io/${GCP_PROJECT}/my-app:${env.BUILD_ID}"
}
stages {
stage('Build') {
steps {
sh 'docker build -t ${GCR_IMAGE} .'
}
}
stage('Push to GCR') {
steps {
withCredentials([file(credentialsId: 'gcp-service-account', variable: 'GCP_SA_KEY')]) {
sh '''
gcloud auth activate-service-account --key-file=${GCP_SA_KEY}
gcloud auth configure-docker
docker push ${GCR_IMAGE}
'''
}
}
}
stage('Security Scan') {
steps {
script {
// Wait for GCR scan to complete
sleep time: 60, unit: 'SECONDS'
// Call our scanner service
def scanResult = httpRequest (
url: "http://scanner-service/api/gcr/security/validate-policy",
httpMode: 'POST',
contentType: 'APPLICATION_JSON',
requestBody: """
{
"imageUrl": "${GCR_IMAGE}",
"policy": {
"maxCriticalVulnerabilities": 0,
"maxHighVulnerabilities": 2,
"maxTotalVulnerabilities": 20
}
}
""",
validResponseCodes: '200'
)
def result = readJSON text: scanResult.content
if (!result.validationResult.compliant) {
error "Image failed security policy. Violations: ${result.validationResult.violations}"
}
}
}
}
stage('Deploy') {
when {
expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' }
}
steps {
echo 'Deploying secure image...'
// Deployment steps here
}
}
}
post {
always {
echo 'Security scan completed'
archiveArtifacts artifacts: 'scan-result.json', fingerprint: true
}
}
}
Configuration
1. Application Properties
# application.yml
gcp:
project:
id: ${GCP_PROJECT:your-project-id}
credentials:
file: ${GOOGLE_APPLICATION_CREDENTIALS:}
gcr:
scanner:
timeout:
seconds: 30
policy:
default: production
production:
max-critical: 0
max-high: 2
max-medium: 10
max-total: 50
development:
max-critical: 1
max-high: 5
max-medium: 20
max-total: 100
server:
port: 8080
logging:
level:
com.example.gcr.scanner: INFO
Best Practices
- Automate Scanning: Integrate security scans into CI/CD pipelines
- Policy Enforcement: Use strict policies for production, lenient for development
- Regular Updates: Keep base images and dependencies updated
- Monitoring: Track vulnerability trends over time
- Remediation: Automate fixes for common vulnerabilities
- Compliance: Align with organizational security standards
// Example of automated remediation
public class AutoRemediation {
public boolean shouldAutoFix(Vulnerability vulnerability) {
return vulnerability.isFixAvailable() &&
vulnerability.getSeverity().isAtLeast(Severity.HIGH) &&
!vulnerability.getAffectedPackage().contains("kernel");
}
}
Conclusion
GCR Vulnerability Scanning with Java provides:
- Automated security scanning integrated with GCR
- Custom security policies and compliance checking
- Risk assessment and trend analysis
- CI/CD integration for automated security gates
- Comprehensive reporting and recommendations
By implementing this comprehensive GCR scanning solution, you can ensure container images meet security standards before deployment, maintain compliance with organizational policies, and continuously monitor for new vulnerabilities in your container ecosystem.