Article
Google Container Registry (GCR) Vulnerability Scanning provides automated security scanning for container images stored in GCR. It identifies vulnerabilities in your container images and provides detailed reports to help you maintain secure deployments. This guide covers everything from basic setup to advanced integration patterns for Java applications.
Why GCR Vulnerability Scanning for Java?
- Automated Scanning: Automatic vulnerability detection for every pushed image
- Comprehensive Database: Uses Google's vulnerability database updated continuously
- CI/CD Integration: Seamless integration with Cloud Build and other CI/CD tools
- Detailed Reporting: Provides CVE details, severity levels, and fix recommendations
- Policy Enforcement: Supports security policies and break-glass procedures
- Cost-Effective: Native integration with Google Cloud Platform
Project Setup and Dependencies
Maven Dependencies:
<properties>
<google.cloud.version>2.20.0</google.cloud.version>
<jib.version>3.4.0</jib.version>
</properties>
<dependencies>
<!-- Google Cloud Container -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-container</artifactId>
<version>${google.cloud.version}</version>
</dependency>
<!-- Google Cloud Build -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-build</artifactId>
<version>${google.cloud.version}</version>
</dependency>
<!-- Google Cloud Storage for reports -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-storage</artifactId>
<version>${google.cloud.version}</version>
</dependency>
<!-- Jib for container building -->
<dependency>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>${jib.version}</version>
</dependency>
</dependencies>
1. Container Image Configuration
Dockerfile with Security Best Practices:
# Use minimal base image with known vulnerabilities FROM eclipse-temurin:17-jre-alpine as builder # Security: Run as non-root user RUN addgroup -S spring && adduser -S spring -G spring USER spring:spring # Security: Use separate layer for dependencies COPY --chown=spring:spring target/*.jar app.jar # Security: Use least privilege RUN chmod 755 app.jar # Security: Use specific version tags FROM eclipse-temurin:17-jre-alpine # Install security updates RUN apk update && apk upgrade && rm -rf /var/cache/apk/* # Create non-root user RUN addgroup -S spring && adduser -S spring -G spring USER spring:spring # Copy application COPY --from=builder /home/spring/app.jar /app/app.jar # Security: Use read-only filesystem where possible VOLUME /tmp # Security: Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8080/actuator/health || exit 1 # Security: Use explicit ports EXPOSE 8080 # Security: Use exec form ENTRYPOINT ["java", "-jar", "/app/app.jar"]
Jib Configuration for Secure Images:
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<from>
<image>eclipse-temurin:17-jre-alpine</image>
<platforms>
<platform>
<architecture>amd64</architecture>
<os>linux</os>
</platform>
</platforms>
</from>
<to>
<image>gcr.io/${gcp.project}/order-service:${project.version}</image>
<tags>
<tag>latest</tag>
<tag>${git.commit.id.abbrev}</tag>
</tags>
</to>
<container>
<user>1000:1000</user>
<workingDirectory>/app</workingDirectory>
<entrypoint>
<arg>java</arg>
<arg>-jar</arg>
<arg>/app/app.jar</arg>
</entrypoint>
<ports>
<port>8080</port>
</ports>
<environment>
<JAVA_TOOL_OPTIONS>-Xmx512m -XX:+UseG1GC</JAVA_TOOL_OPTIONS>
</environment>
<labels>
<maintainer>[email protected]</maintainer>
<version>${project.version}</version>
<commit>${git.commit.id.abbrev}</commit>
</labels>
<creationTime>USE_CURRENT_TIMESTAMP</creationTime>
</container>
</configuration>
</plugin>
2. GCR Vulnerability Scanning Service
GCR Scan Service:
package com.example.security.gcr;
import com.google.cloud.devtools.containeranalysis.v1.ContainerAnalysisClient;
import com.google.cloud.devtools.containeranalysis.v1.ContainerAnalysisSettings;
import com.google.cloud.devtools.containeranalysis.v1.stub.ContainerAnalysisStubSettings;
import com.google.containeranalysis.v1.*;
import io.grafeas.v1.*;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class GcrVulnerabilityScanService {
private final ContainerAnalysisClient containerAnalysisClient;
private final String projectId;
private final String location = "global";
public GcrVulnerabilityScanService(GcrConfig gcrConfig) throws IOException {
this.projectId = gcrConfig.getProjectId();
ContainerAnalysisSettings settings = ContainerAnalysisSettings.newBuilder()
.setCredentialsProvider(gcrConfig.getCredentialsProvider())
.build();
this.containerAnalysisClient = ContainerAnalysisClient.create(settings);
}
public VulnerabilityScanReport scanImage(String imageUrl) {
try {
String resourceUrl = getResourceUrl(imageUrl);
// Get occurrences for the image
List<Occurrence> occurrences = getVulnerabilityOccurrences(resourceUrl);
return generateVulnerabilityReport(imageUrl, occurrences);
} catch (Exception e) {
throw new RuntimeException("Failed to scan image: " + imageUrl, e);
}
}
public boolean hasCriticalVulnerabilities(String imageUrl, int maxCritical, int maxHigh) {
VulnerabilityScanReport report = scanImage(imageUrl);
return report.getCriticalCount() > maxCritical ||
report.getHighCount() > maxHigh;
}
public List<VulnerabilityFinding> getCriticalVulnerabilities(String imageUrl) {
VulnerabilityScanReport report = scanImage(imageUrl);
return report.getFindings().stream()
.filter(finding -> finding.getSeverity() == Severity.CRITICAL)
.collect(Collectors.toList());
}
public Map<Severity, List<VulnerabilityFinding>> groupVulnerabilitiesBySeverity(String imageUrl) {
VulnerabilityScanReport report = scanImage(imageUrl);
return report.getFindings().stream()
.collect(Collectors.groupingBy(VulnerabilityFinding::getSeverity));
}
public FixAdvice getFixAdvice(String imageUrl) {
VulnerabilityScanReport report = scanImage(imageUrl);
FixAdvice advice = new FixAdvice();
advice.setImageUrl(imageUrl);
advice.setTotalVulnerabilities(report.getTotalCount());
// Group by package for fix recommendations
Map<String, List<VulnerabilityFinding>> byPackage = report.getFindings().stream()
.collect(Collectors.groupingBy(VulnerabilityFinding::getPackageName));
List<PackageFix> packageFixes = byPackage.entrySet().stream()
.map(entry -> createPackageFix(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
advice.setPackageFixes(packageFixes);
return advice;
}
public ScanHistory getScanHistory(String imageUrl, int days) {
String resourceUrl = getResourceUrl(imageUrl);
ScanHistory history = new ScanHistory();
history.setImageUrl(imageUrl);
history.setDays(days);
// Get scan occurrences for the past N days
List<Occurrence> occurrences = getHistoricalOccurrences(resourceUrl, days);
Map<String, DailyScanResult> dailyResults = occurrences.stream()
.collect(Collectors.groupingBy(
occ -> getScanDate(occ),
Collectors.collectingAndThen(
Collectors.toList(),
this::createDailyResult
)
));
history.setDailyResults(dailyResults);
return history;
}
private List<Occurrence> getVulnerabilityOccurrences(String resourceUrl) {
String projectName = ProjectName.format(projectId);
Filter filter = Filter.newBuilder()
.setFilter(String.format("resourceUrl=\"%s\" AND kind=\"VULNERABILITY\"", resourceUrl))
.build();
ListOccurrencesRequest request = ListOccurrencesRequest.newBuilder()
.setParent(projectName)
.setFilter(filter.toString())
.build();
return containerAnalysisClient.listOccurrences(request).iterateAll()
.stream()
.collect(Collectors.toList());
}
private List<Occurrence> getHistoricalOccurrences(String resourceUrl, int days) {
// Implementation for historical data
// This would typically query occurrences within the date range
return getVulnerabilityOccurrences(resourceUrl);
}
private VulnerabilityScanReport generateVulnerabilityReport(String imageUrl, List<Occurrence> occurrences) {
VulnerabilityScanReport report = new VulnerabilityScanReport();
report.setImageUrl(imageUrl);
report.setScanTime(new Date());
List<VulnerabilityFinding> findings = occurrences.stream()
.map(this::convertToFinding)
.filter(Objects::nonNull)
.collect(Collectors.toList());
report.setFindings(findings);
// Calculate counts
report.setTotalCount(findings.size());
report.setCriticalCount(countBySeverity(findings, Severity.CRITICAL));
report.setHighCount(countBySeverity(findings, Severity.HIGH));
report.setMediumCount(countBySeverity(findings, Severity.MEDIUM));
report.setLowCount(countBySeverity(findings, Severity.LOW));
return report;
}
private VulnerabilityFinding convertToFinding(Occurrence occurrence) {
if (!occurrence.hasVulnerability()) {
return null;
}
Vulnerability vulnerability = occurrence.getVulnerability();
VulnerabilityFinding finding = new VulnerabilityFinding();
finding.setCveId(vulnerability.getCve());
finding.setSeverity(convertSeverity(vulnerability.getEffectiveSeverity()));
finding.setDescription(vulnerability.getShortDescription());
// Extract package information
if (vulnerability.getPackageIssueCount() > 0) {
PackageIssue packageIssue = vulnerability.getPackageIssue(0);
finding.setPackageName(packageIssue.getAffectedPackage());
finding.setInstalledVersion(packageIssue.getAffectedVersion().getFullName());
finding.setFixedVersion(packageIssue.getFixedVersion().getFullName());
}
finding.setCvssScore(vulnerability.getCvssScore());
finding.setRelatedUrls(vulnerability.getRelatedUrlsList());
return finding;
}
private PackageFix createPackageFix(String packageName, List<VulnerabilityFinding> findings) {
PackageFix fix = new PackageFix();
fix.setPackageName(packageName);
fix.setVulnerabilityCount(findings.size());
// Find the highest fixed version across all vulnerabilities
Optional<String> maxFixedVersion = findings.stream()
.map(VulnerabilityFinding::getFixedVersion)
.filter(Objects::nonNull)
.max(Comparator.naturalOrder());
maxFixedVersion.ifPresent(fix::setRecommendedVersion);
fix.setCriticalCount(countBySeverity(findings, Severity.CRITICAL));
fix.setHighCount(countBySeverity(findings, Severity.HIGH));
return fix;
}
private DailyScanResult createDailyResult(List<Occurrence> occurrences) {
DailyScanResult result = new DailyScanResult();
result.setScanDate(new Date());
List<VulnerabilityFinding> findings = occurrences.stream()
.map(this::convertToFinding)
.filter(Objects::nonNull)
.collect(Collectors.toList());
result.setTotalVulnerabilities(findings.size());
result.setNewVulnerabilities(0); // Would compare with previous day
result.setFixedVulnerabilities(0); // Would compare with previous day
return result;
}
private String getResourceUrl(String imageUrl) {
return "https://" + imageUrl;
}
private String getScanDate(Occurrence occurrence) {
return occurrence.getCreateTime().toString(); // Format as needed
}
private Severity convertSeverity(io.grafeas.v1.Severity severity) {
switch (severity) {
case CRITICAL: return Severity.CRITICAL;
case HIGH: return Severity.HIGH;
case MEDIUM: return Severity.MEDIUM;
case LOW: return Severity.LOW;
case MINIMAL: return Severity.LOW;
case SEVERITY_UNSPECIFIED: return Severity.UNKNOWN;
default: return Severity.UNKNOWN;
}
}
private long countBySeverity(List<VulnerabilityFinding> findings, Severity severity) {
return findings.stream()
.filter(finding -> finding.getSeverity() == severity)
.count();
}
}
// Configuration class
@Component
class GcrConfig {
private String projectId;
private CredentialsProvider credentialsProvider;
// Getters and setters
public String getProjectId() { return projectId; }
public void setProjectId(String projectId) { this.projectId = projectId; }
public CredentialsProvider getCredentialsProvider() { return credentialsProvider; }
public void setCredentialsProvider(CredentialsProvider credentialsProvider) {
this.credentialsProvider = credentialsProvider;
}
}
// Report classes
class VulnerabilityScanReport {
private String imageUrl;
private Date scanTime;
private int totalCount;
private int criticalCount;
private int highCount;
private int mediumCount;
private int lowCount;
private List<VulnerabilityFinding> findings;
// Getters and setters
public String getImageUrl() { return imageUrl; }
public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
public Date getScanTime() { return scanTime; }
public void setScanTime(Date scanTime) { this.scanTime = scanTime; }
public int getTotalCount() { return totalCount; }
public void setTotalCount(int totalCount) { this.totalCount = totalCount; }
public int getCriticalCount() { return criticalCount; }
public void setCriticalCount(int criticalCount) { this.criticalCount = criticalCount; }
public int getHighCount() { return highCount; }
public void setHighCount(int highCount) { this.highCount = highCount; }
public int getMediumCount() { return mediumCount; }
public void setMediumCount(int mediumCount) { this.mediumCount = mediumCount; }
public int getLowCount() { return lowCount; }
public void setLowCount(int lowCount) { this.lowCount = lowCount; }
public List<VulnerabilityFinding> getFindings() { return findings; }
public void setFindings(List<VulnerabilityFinding> findings) { this.findings = findings; }
}
class VulnerabilityFinding {
private String cveId;
private Severity severity;
private String description;
private String packageName;
private String installedVersion;
private String fixedVersion;
private float cvssScore;
private List<RelatedUrl> relatedUrls;
// Getters and setters
public String getCveId() { return cveId; }
public void setCveId(String cveId) { this.cveId = cveId; }
public Severity getSeverity() { return severity; }
public void setSeverity(Severity severity) { this.severity = severity; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getPackageName() { return packageName; }
public void setPackageName(String packageName) { this.packageName = packageName; }
public String getInstalledVersion() { return installedVersion; }
public void setInstalledVersion(String installedVersion) { this.installedVersion = installedVersion; }
public String getFixedVersion() { return fixedVersion; }
public void setFixedVersion(String fixedVersion) { this.fixedVersion = fixedVersion; }
public float getCvssScore() { return cvssScore; }
public void setCvssScore(float cvssScore) { this.cvssScore = cvssScore; }
public List<RelatedUrl> getRelatedUrls() { return relatedUrls; }
public void setRelatedUrls(List<RelatedUrl> relatedUrls) { this.relatedUrls = relatedUrls; }
}
class FixAdvice {
private String imageUrl;
private int totalVulnerabilities;
private List<PackageFix> packageFixes;
// Getters and setters
public String getImageUrl() { return imageUrl; }
public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
public int getTotalVulnerabilities() { return totalVulnerabilities; }
public void setTotalVulnerabilities(int totalVulnerabilities) { this.totalVulnerabilities = totalVulnerabilities; }
public List<PackageFix> getPackageFixes() { return packageFixes; }
public void setPackageFixes(List<PackageFix> packageFixes) { this.packageFixes = packageFixes; }
}
class PackageFix {
private String packageName;
private String recommendedVersion;
private int vulnerabilityCount;
private int criticalCount;
private int highCount;
// Getters and setters
public String getPackageName() { return packageName; }
public void setPackageName(String packageName) { this.packageName = packageName; }
public String getRecommendedVersion() { return recommendedVersion; }
public void setRecommendedVersion(String recommendedVersion) { this.recommendedVersion = recommendedVersion; }
public int getVulnerabilityCount() { return vulnerabilityCount; }
public void setVulnerabilityCount(int vulnerabilityCount) { this.vulnerabilityCount = vulnerabilityCount; }
public int getCriticalCount() { return criticalCount; }
public void setCriticalCount(int criticalCount) { this.criticalCount = criticalCount; }
public int getHighCount() { return highCount; }
public void setHighCount(int highCount) { this.highCount = highCount; }
}
class ScanHistory {
private String imageUrl;
private int days;
private Map<String, DailyScanResult> dailyResults;
// Getters and setters
public String getImageUrl() { return imageUrl; }
public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
public int getDays() { return days; }
public void setDays(int days) { this.days = days; }
public Map<String, DailyScanResult> getDailyResults() { return dailyResults; }
public void setDailyResults(Map<String, DailyScanResult> dailyResults) { this.dailyResults = dailyResults; }
}
class DailyScanResult {
private Date scanDate;
private int totalVulnerabilities;
private int newVulnerabilities;
private int fixedVulnerabilities;
// Getters and setters
public Date getScanDate() { return scanDate; }
public void setScanDate(Date scanDate) { this.scanDate = scanDate; }
public int getTotalVulnerabilities() { return totalVulnerabilities; }
public void setTotalVulnerabilities(int totalVulnerabilities) { this.totalVulnerabilities = totalVulnerabilities; }
public int getNewVulnerabilities() { return newVulnerabilities; }
public void setNewVulnerabilities(int newVulnerabilities) { this.newVulnerabilities = newVulnerabilities; }
public int getFixedVulnerabilities() { return fixedVulnerabilities; }
public void setFixedVulnerabilities(int fixedVulnerabilities) { this.fixedVulnerabilities = fixedVulnerabilities; }
}
enum Severity {
CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN
}
class RelatedUrl {
private String url;
private String label;
// Getters and setters
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getLabel() { return label; }
public void setLabel(String label) { this.label = label; }
}
3. Cloud Build Integration
cloudbuild.yaml with Vulnerability Scanning:
steps: # Build the application - name: 'maven:3.8.5-eclipse-temurin-17' entrypoint: 'mvn' args: ['clean', 'compile', 'package', '-DskipTests'] id: 'build' # Run tests - name: 'maven:3.8.5-eclipse-temurin-17' entrypoint: 'mvn' args: ['test'] id: 'test' # Build container image with Jib - name: 'gcr.io/cloud-builders/mvn' args: ['compile', 'jib:build', '-DskipTests'] id: 'build-image' env: - 'JIB_TO_IMAGE=gcr.io/$PROJECT_ID/order-service:$COMMIT_SHA' - 'JIB_TO_IMAGE_TAGS=gcr.io/$PROJECT_ID/order-service:latest' # Run security scan - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk' entrypoint: 'bash' args: - '-c' - | gcloud artifacts docker images scan gcr.io/$PROJECT_ID/order-service:$COMMIT_SHA \ --format=json > scan-results.json id: 'vulnerability-scan' # Check scan results - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk' entrypoint: 'bash' args: - '-c' - | # Check for critical vulnerabilities CRITICAL_COUNT=$(cat scan-results.json | jq '.scan.vulnerabilities.severityCounts.CRITICAL // 0') HIGH_COUNT=$(cat scan-results.json | jq '.scan.vulnerabilities.severityCounts.HIGH // 0') echo "Critical vulnerabilities: $CRITICAL_COUNT" echo "High vulnerabilities: $HIGH_COUNT" if [ $CRITICAL_COUNT -gt 0 ] || [ $HIGH_COUNT -gt 5 ]; then echo "❌ Too many critical/high vulnerabilities found" exit 1 else echo "✅ Security scan passed" fi id: 'check-scan-results' # Deploy to staging if scan passes - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk' entrypoint: 'gcloud' args: - 'run' - 'deploy' - 'order-service-staging' - '--image=gcr.io/$PROJECT_ID/order-service:$COMMIT_SHA' - '--region=us-central1' - '--platform=managed' - '--allow-unauthenticated' id: 'deploy-staging' waitFor: ['check-scan-results'] # Security scanning configuration options: logging: CLOUD_LOGGING_ONLY # Artifact results artifacts: objects: location: 'gs://$_PROJECT_ID-build-artifacts/order-service/' paths: ['scan-results.json'] # Timeouts timeout: 1800s # Machine type options: machineType: 'E2_HIGHCPU_8'
4. Security Policy Enforcement
Security Policy Service:
package com.example.security.gcr;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class SecurityPolicyService {
public SecurityPolicyResult evaluatePolicy(VulnerabilityScanReport report,
SecurityPolicy policy) {
SecurityPolicyResult result = new SecurityPolicyResult();
result.setImageUrl(report.getImageUrl());
result.setScanTime(report.getScanTime());
boolean passes = true;
StringBuilder reasons = new StringBuilder();
// Check critical vulnerabilities
if (report.getCriticalCount() > policy.getMaxCriticalVulnerabilities()) {
passes = false;
reasons.append(String.format("Critical vulnerabilities exceed limit: %d > %d. ",
report.getCriticalCount(), policy.getMaxCriticalVulnerabilities()));
}
// Check high vulnerabilities
if (report.getHighCount() > policy.getMaxHighVulnerabilities()) {
passes = false;
reasons.append(String.format("High vulnerabilities exceed limit: %d > %d. ",
report.getHighCount(), policy.getMaxHighVulnerabilities()));
}
// Check total vulnerabilities
if (report.getTotalCount() > policy.getMaxTotalVulnerabilities()) {
passes = false;
reasons.append(String.format("Total vulnerabilities exceed limit: %d > %d. ",
report.getTotalCount(), policy.getMaxTotalVulnerabilities()));
}
// Check for specific CVEs
for (VulnerabilityFinding finding : report.getFindings()) {
if (policy.getBlockedCves().contains(finding.getCveId())) {
passes = false;
reasons.append(String.format("Blocked CVE found: %s. ", finding.getCveId()));
}
}
// Check CVSS score threshold
for (VulnerabilityFinding finding : report.getFindings()) {
if (finding.getCvssScore() >= policy.getMaxCvssScore()) {
passes = false;
reasons.append(String.format("High CVSS score: %s = %.1f. ",
finding.getCveId(), finding.getCvssScore()));
}
}
result.setPasses(passes);
result.setRejectionReasons(reasons.toString());
return result;
}
public BreakGlassAuthorization authorizeBreakGlass(String imageUrl,
String reason,
String approver) {
BreakGlassAuthorization auth = new BreakGlassAuthorization();
auth.setImageUrl(imageUrl);
auth.setReason(reason);
auth.setApprover(approver);
auth.setAuthorizationTime(new Date());
auth.setExpirationTime(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000)); // 24 hours
// In production, this would create a temporary exception in the policy system
auth.setAuthorizationId(generateAuthorizationId());
return auth;
}
private String generateAuthorizationId() {
return "BG-" + System.currentTimeMillis();
}
}
class SecurityPolicy {
private int maxCriticalVulnerabilities = 0;
private int maxHighVulnerabilities = 5;
private int maxTotalVulnerabilities = 50;
private float maxCvssScore = 8.0f;
private Map<String, String> blockedCves = Map.of(
"CVE-2021-44228", "Log4Shell vulnerability",
"CVE-2021-45046", "Log4Shell follow-up"
);
private boolean allowBreakGlass = true;
private int breakGlassDurationHours = 24;
// 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 getMaxTotalVulnerabilities() { return maxTotalVulnerabilities; }
public void setMaxTotalVulnerabilities(int maxTotalVulnerabilities) { this.maxTotalVulnerabilities = maxTotalVulnerabilities; }
public float getMaxCvssScore() { return maxCvssScore; }
public void setMaxCvssScore(float maxCvssScore) { this.maxCvssScore = maxCvssScore; }
public Map<String, String> getBlockedCves() { return blockedCves; }
public void setBlockedCves(Map<String, String> blockedCves) { this.blockedCves = blockedCves; }
public boolean isAllowBreakGlass() { return allowBreakGlass; }
public void setAllowBreakGlass(boolean allowBreakGlass) { this.allowBreakGlass = allowBreakGlass; }
public int getBreakGlassDurationHours() { return breakGlassDurationHours; }
public void setBreakGlassDurationHours(int breakGlassDurationHours) { this.breakGlassDurationHours = breakGlassDurationHours; }
}
class SecurityPolicyResult {
private String imageUrl;
private Date scanTime;
private boolean passes;
private String rejectionReasons;
// Getters and setters
public String getImageUrl() { return imageUrl; }
public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
public Date getScanTime() { return scanTime; }
public void setScanTime(Date scanTime) { this.scanTime = scanTime; }
public boolean isPasses() { return passes; }
public void setPasses(boolean passes) { this.passes = passes; }
public String getRejectionReasons() { return rejectionReasons; }
public void setRejectionReasons(String rejectionReasons) { this.rejectionReasons = rejectionReasons; }
}
class BreakGlassAuthorization {
private String authorizationId;
private String imageUrl;
private String reason;
private String approver;
private Date authorizationTime;
private Date expirationTime;
// Getters and setters
public String getAuthorizationId() { return authorizationId; }
public void setAuthorizationId(String authorizationId) { this.authorizationId = authorizationId; }
public String getImageUrl() { return imageUrl; }
public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
public String getReason() { return reason; }
public void setReason(String reason) { this.reason = reason; }
public String getApprover() { return approver; }
public void setApprover(String approver) { this.approver = approver; }
public Date getAuthorizationTime() { return authorizationTime; }
public void setAuthorizationTime(Date authorizationTime) { this.authorizationTime = authorizationTime; }
public Date getExpirationTime() { return expirationTime; }
public void setExpirationTime(Date expirationTime) { this.expirationTime = expirationTime; }
}
5. REST API Controllers
GCR Security Controller:
package com.example.security.controller;
import com.example.security.gcr.*;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/security/gcr")
public class GcrSecurityController {
private final GcrVulnerabilityScanService scanService;
private final SecurityPolicyService policyService;
public GcrSecurityController(GcrVulnerabilityScanService scanService,
SecurityPolicyService policyService) {
this.scanService = scanService;
this.policyService = policyService;
}
@GetMapping("/scan/{imageUrl}")
public ResponseEntity<VulnerabilityScanReport> scanImage(
@PathVariable String imageUrl) {
VulnerabilityScanReport report = scanService.scanImage(imageUrl);
return ResponseEntity.ok(report);
}
@GetMapping("/scan/{imageUrl}/policy-check")
public ResponseEntity<SecurityPolicyResult> checkPolicyCompliance(
@PathVariable String imageUrl,
@RequestBody SecurityPolicy policy) {
VulnerabilityScanReport report = scanService.scanImage(imageUrl);
SecurityPolicyResult result = policyService.evaluatePolicy(report, policy);
return ResponseEntity.ok(result);
}
@GetMapping("/scan/{imageUrl}/critical")
public ResponseEntity<List<VulnerabilityFinding>> getCriticalVulnerabilities(
@PathVariable String imageUrl) {
List<VulnerabilityFinding> criticalVulns = scanService.getCriticalVulnerabilities(imageUrl);
return ResponseEntity.ok(criticalVulns);
}
@GetMapping("/scan/{imageUrl}/fix-advice")
public ResponseEntity<FixAdvice> getFixAdvice(
@PathVariable String imageUrl) {
FixAdvice advice = scanService.getFixAdvice(imageUrl);
return ResponseEntity.ok(advice);
}
@GetMapping("/scan/{imageUrl}/history")
public ResponseEntity<ScanHistory> getScanHistory(
@PathVariable String imageUrl,
@RequestParam(defaultValue = "7") int days) {
ScanHistory history = scanService.getScanHistory(imageUrl, days);
return ResponseEntity.ok(history);
}
@PostMapping("/break-glass")
public ResponseEntity<BreakGlassAuthorization> authorizeBreakGlass(
@RequestBody BreakGlassRequest request) {
BreakGlassAuthorization auth = policyService.authorizeBreakGlass(
request.getImageUrl(),
request.getReason(),
request.getApprover()
);
return ResponseEntity.ok(auth);
}
@GetMapping("/dashboard")
public ResponseEntity<SecurityDashboard> getSecurityDashboard(
@RequestParam List<String> images) {
SecurityDashboard dashboard = new SecurityDashboard();
dashboard.setGeneratedAt(new Date());
for (String imageUrl : images) {
VulnerabilityScanReport report = scanService.scanImage(imageUrl);
SecurityPolicyResult policyResult = policyService.evaluatePolicy(report, new SecurityPolicy());
ImageSecurityStatus status = new ImageSecurityStatus();
status.setImageUrl(imageUrl);
status.setScanReport(report);
status.setPolicyResult(policyResult);
dashboard.getImageStatuses().add(status);
}
return ResponseEntity.ok(dashboard);
}
}
class BreakGlassRequest {
private String imageUrl;
private String reason;
private String approver;
// Getters and setters
public String getImageUrl() { return imageUrl; }
public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
public String getReason() { return reason; }
public void setReason(String reason) { this.reason = reason; }
public String getApprover() { return approver; }
public void setApprover(String approver) { this.approver = approver; }
}
class SecurityDashboard {
private Date generatedAt;
private List<ImageSecurityStatus> imageStatuses = new ArrayList<>();
// Getters and setters
public Date getGeneratedAt() { return generatedAt; }
public void setGeneratedAt(Date generatedAt) { this.generatedAt = generatedAt; }
public List<ImageSecurityStatus> getImageStatuses() { return imageStatuses; }
public void setImageStatuses(List<ImageSecurityStatus> imageStatuses) { this.imageStatuses = imageStatuses; }
}
class ImageSecurityStatus {
private String imageUrl;
private VulnerabilityScanReport scanReport;
private SecurityPolicyResult policyResult;
// Getters and setters
public String getImageUrl() { return imageUrl; }
public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
public VulnerabilityScanReport getScanReport() { return scanReport; }
public void setScanReport(VulnerabilityScanReport scanReport) { this.scanReport = scanReport; }
public SecurityPolicyResult getPolicyResult() { return policyResult; }
public void setPolicyResult(SecurityPolicyResult policyResult) { this.policyResult = policyResult; }
}
6. GitHub Actions Integration
.github/workflows/gcr-security-scan.yml:
name: GCR Security Scan
on:
push:
branches: [ main, develop ]
paths: ['src/**', 'pom.xml', 'Dockerfile']
pull_request:
branches: [ main, develop ]
schedule:
- cron: '0 6 * * 1' # Weekly on Monday at 6 AM
env:
PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
SERVICE_NAME: order-service
REGION: us-central1
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: 'maven'
- name: Build with Maven
run: mvn clean compile package -DskipTests
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v1
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v1
- name: Configure Docker
run: gcloud auth configure-docker
- name: Build and push container
run: |
mvn compile jib:build \
-Djib.to.image=gcr.io/$PROJECT_ID/$SERVICE_NAME:${{ github.sha }} \
-DskipTests
- name: Run GCR Vulnerability Scan
run: |
gcloud artifacts docker images scan gcr.io/$PROJECT_ID/$SERVICE_NAME:${{ github.sha }} \
--format=json > scan-results.json
- name: Analyze Scan Results
run: |
# Install jq for JSON parsing
sudo apt-get install jq
# Extract vulnerability counts
CRITICAL=$(jq '.scan.vulnerabilities.severityCounts.CRITICAL // 0' scan-results.json)
HIGH=$(jq '.scan.vulnerabilities.severityCounts.HIGH // 0' scan-results.json)
MEDIUM=$(jq '.scan.vulnerabilities.severityCounts.MEDIUM // 0' scan-results.json)
LOW=$(jq '.scan.vulnerabilities.severityCounts.LOW // 0' scan-results.json)
echo "Vulnerability Summary:"
echo "Critical: $CRITICAL"
echo "High: $HIGH"
echo "Medium: $MEDIUM"
echo "Low: $LOW"
# Check against security policy
if [ $CRITICAL -gt 0 ]; then
echo "❌ CRITICAL vulnerabilities found - failing build"
exit 1
elif [ $HIGH -gt 3 ]; then
echo "❌ Too many HIGH vulnerabilities found - failing build"
exit 1
else
echo "✅ Security scan passed"
fi
- name: Upload Scan Report
uses: actions/upload-artifact@v3
with:
name: gcr-scan-report
path: scan-results.json
retention-days: 30
- name: Notify Security Team
if: failure()
uses: 8398a7/action-slack@v3
with:
status: failure
text: |
GCR Security Scan Failed for ${{ github.repository }}
Commit: ${{ github.sha }}
Critical Vulnerabilities: ${{ env.CRITICAL_COUNT }}
High Vulnerabilities: ${{ env.HIGH_COUNT }}
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
env:
CRITICAL_COUNT: ${{ env.CRITICAL }}
HIGH_COUNT: ${{ env.HIGH }}
7. Monitoring and Alerting
Security Monitoring Service:
package com.example.security.gcr;
import com.google.cloud.monitoring.v3.MetricServiceClient;
import com.google.monitoring.v3.*;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.*;
@Service
public class SecurityMonitoringService {
private final GcrVulnerabilityScanService scanService;
private final MetricServiceClient metricServiceClient;
private final String projectId;
public SecurityMonitoringService(GcrVulnerabilityScanService scanService,
String projectId) throws IOException {
this.scanService = scanService;
this.projectId = projectId;
this.metricServiceClient = MetricServiceClient.create();
}
@Scheduled(cron = "0 0 6 * * *") // Daily at 6 AM
public void dailySecurityReport() {
// Scan all production images
List<String> productionImages = getProductionImages();
SecurityReport report = new SecurityReport();
report.setReportDate(new Date());
for (String image : productionImages) {
VulnerabilityScanReport scanReport = scanService.scanImage(image);
report.getImageReports().put(image, scanReport);
// Send metrics to Cloud Monitoring
sendSecurityMetrics(image, scanReport);
}
// Generate and send report
generateSecurityReport(report);
}
public void sendSecurityAlert(String imageUrl, VulnerabilityFinding finding) {
// Implementation to send alerts via email, Slack, etc.
String alertMessage = String.format(
"🚨 Security Alert: %s found in %s\n" +
"Severity: %s\n" +
"CVSS Score: %.1f\n" +
"Description: %s",
finding.getCveId(), imageUrl, finding.getSeverity(),
finding.getCvssScore(), finding.getDescription()
);
// Send to alerting system
sendAlert(alertMessage);
}
private void sendSecurityMetrics(String imageUrl, VulnerabilityScanReport report) {
String customMetricPrefix = "custom.googleapis.com/security/";
// Send vulnerability counts
writeTimeSeries(customMetricPrefix + "critical_vulnerabilities",
imageUrl, report.getCriticalCount());
writeTimeSeries(customMetricPrefix + "high_vulnerabilities",
imageUrl, report.getHighCount());
writeTimeSeries(customMetricPrefix + "total_vulnerabilities",
imageUrl, report.getTotalCount());
}
private void writeTimeSeries(String metricType, String imageUrl, long value) {
ProjectName name = ProjectName.of(projectId);
TimeInterval interval = TimeInterval.newBuilder()
.setEndTime(com.google.protobuf.Timestamp.newBuilder()
.setSeconds(System.currentTimeMillis() / 1000))
.build();
TypedValue pointValue = TypedValue.newBuilder()
.setInt64Value(value)
.build();
Point point = Point.newBuilder()
.setInterval(interval)
.setValue(pointValue)
.build();
TimeSeries timeSeries = TimeSeries.newBuilder()
.setMetric(Metric.newBuilder()
.setType(metricType)
.putLabels("image", imageUrl))
.setResource(MonitoredResource.newBuilder()
.setType("global")
.putLabels("project_id", projectId))
.addPoints(point)
.build();
metricServiceClient.createTimeSeries(name, List.of(timeSeries));
}
private List<String> getProductionImages() {
// Implementation to get list of production images
return List.of(
"gcr.io/my-project/order-service:latest",
"gcr.io/my-project/user-service:latest",
"gcr.io/my-project/payment-service:latest"
);
}
private void generateSecurityReport(SecurityReport report) {
// Implementation to generate and send daily security report
}
private void sendAlert(String message) {
// Implementation to send alerts
}
}
class SecurityReport {
private Date reportDate;
private Map<String, VulnerabilityScanReport> imageReports = new HashMap<>();
// Getters and setters
public Date getReportDate() { return reportDate; }
public void setReportDate(Date reportDate) { this.reportDate = reportDate; }
public Map<String, VulnerabilityScanReport> getImageReports() { return imageReports; }
public void setImageReports(Map<String, VulnerabilityScanReport> imageReports) { this.imageReports = imageReports; }
}
8. Best Practices and Configuration
application.yml:
gcp:
project-id: ${GCP_PROJECT_ID:my-project}
credentials:
location: ${GOOGLE_APPLICATION_CREDENTIALS:}
security:
gcr:
enabled: true
scan-on-startup: false
policies:
max-critical: 0
max-high: 3
max-total: 50
blocked-cves:
- CVE-2021-44228
- CVE-2021-45046
monitoring:
enabled: true
daily-report-time: "06:00"
alerts:
enabled: true
critical-severity: true
slack-webhook: ${SLACK_SECURITY_WEBHOOK}
logging:
level:
com.example.security.gcr: DEBUG
Conclusion
GCR Vulnerability Scanning provides comprehensive container security for Java applications. Key benefits include:
- Automated Scanning: Automatic vulnerability detection for every container image
- Comprehensive Coverage: Scans OS packages and application dependencies
- CI/CD Integration: Seamless integration with Cloud Build and GitHub Actions
- Policy Enforcement: Configurable security policies with break-glass procedures
- Detailed Reporting: CVE details, severity levels, and fix recommendations
- Cost-Effective: Native GCP integration with minimal setup
By implementing the patterns shown above, you can establish a robust container security program that identifies vulnerabilities early, enforces security policies, and maintains the security posture of your Java applications throughout their lifecycle.