Quay Security in Java: Complete Container Security Guide

Introduction to Quay Security

Quay Security refers to the security features and scanning capabilities of Red Hat Quay, a container registry that provides vulnerability scanning, security policy enforcement, and image signing. It helps secure container images throughout the development lifecycle.

Key Features

  • Vulnerability Scanning: Automated CVE scanning of container images
  • Security Policies: Enforce security standards across teams
  • Image Signing: Cryptographic verification of image integrity
  • Access Control: Fine-grained permissions and RBAC
  • Compliance Reporting: Generate security compliance reports

Implementation Guide

Dependencies

Add to your pom.xml:

<properties>
<quay.client.version>1.0.0</quay.client.version>
<jackson.version>2.15.2</jackson.version>
<okhttp.version>4.11.0</okhttp.version>
<jose4j.version>0.9.3</jose4j.version>
</properties>
<dependencies>
<!-- HTTP Client -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- JWT for authentication -->
<dependency>
<groupId>org.bitbucket.b_c</groupId>
<artifactId>jose4j</artifactId>
<version>${jose4j.version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<!-- Docker Java API -->
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java</artifactId>
<version>3.3.0</version>
</dependency>
<!-- For file operations -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.13.0</version>
</dependency>
</dependencies>

Core Quay Security Implementation

Quay Client Setup

package com.example.quay.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class QuayClient {
private static final Logger logger = LoggerFactory.getLogger(QuayClient.class);
private final OkHttpClient httpClient;
private final ObjectMapper objectMapper;
private final String baseUrl;
private final String authToken;
public QuayClient(String baseUrl, String authToken, int timeoutSeconds) {
this.baseUrl = baseUrl.endsWith("/") ? baseUrl : baseUrl + "/";
this.authToken = authToken;
this.objectMapper = new ObjectMapper();
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(timeoutSeconds, TimeUnit.SECONDS)
.readTimeout(timeoutSeconds, TimeUnit.SECONDS)
.writeTimeout(timeoutSeconds, TimeUnit.SECONDS)
.addInterceptor(new AuthInterceptor(authToken))
.build();
logger.info("Quay client initialized for: {}", baseUrl);
}
public QuayClient(String baseUrl, String username, String password, int timeoutSeconds) {
this(baseUrl, generateBasicAuthToken(username, password), timeoutSeconds);
}
private static String generateBasicAuthToken(String username, String password) {
String credentials = username + ":" + password;
return "Basic " + java.util.Base64.getEncoder().encodeToString(credentials.getBytes());
}
public boolean testConnection() throws IOException {
Request request = new Request.Builder()
.url(baseUrl + "api/v1/discovery")
.get()
.build();
try (Response response = httpClient.newCall(request).execute()) {
return response.isSuccessful();
}
}
// Repository operations
public RepositoryList listRepositories(String namespace) throws IOException {
String url = namespace != null ? 
baseUrl + "api/v1/repository?namespace=" + namespace :
baseUrl + "api/v1/repository";
Request request = new Request.Builder()
.url(url)
.get()
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Failed to list repositories: " + response.code());
}
ResponseBody body = response.body();
if (body != null) {
return objectMapper.readValue(body.string(), RepositoryList.class);
}
throw new IOException("Empty response body");
}
}
public RepositoryDetail getRepository(String namespace, String repository) throws IOException {
String url = baseUrl + "api/v1/repository/" + namespace + "/" + repository;
Request request = new Request.Builder()
.url(url)
.get()
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Failed to get repository: " + response.code());
}
ResponseBody body = response.body();
if (body != null) {
return objectMapper.readValue(body.string(), RepositoryDetail.class);
}
throw new IOException("Empty response body");
}
}
// Security scanning operations
public SecurityScanResult getSecurityScan(String namespace, String repository, String tag) 
throws IOException {
String url = baseUrl + "api/v1/repository/" + namespace + "/" + repository + 
"/manifest/" + tag + "/security";
Request request = new Request.Builder()
.url(url)
.get()
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Failed to get security scan: " + response.code());
}
ResponseBody body = response.body();
if (body != null) {
return objectMapper.readValue(body.string(), SecurityScanResult.class);
}
throw new IOException("Empty response body");
}
}
public void triggerSecurityScan(String namespace, String repository, String tag) 
throws IOException {
String url = baseUrl + "api/v1/repository/" + namespace + "/" + repository + 
"/manifest/" + tag + "/security";
RequestBody body = RequestBody.create("{}", MediaType.parse("application/json"));
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Failed to trigger security scan: " + response.code());
}
}
}
// Vulnerability operations
public VulnerabilityReport getVulnerabilityReport(String namespace, String repository, 
String tag) throws IOException {
SecurityScanResult scanResult = getSecurityScan(namespace, repository, tag);
return new VulnerabilityReport(namespace, repository, tag, scanResult);
}
public ImageSigningStatus getSigningStatus(String namespace, String repository, String tag) 
throws IOException {
String url = baseUrl + "api/v1/repository/" + namespace + "/" + repository + 
"/manifest/" + tag + "/signatures";
Request request = new Request.Builder()
.url(url)
.get()
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Failed to get signing status: " + response.code());
}
ResponseBody body = response.body();
if (body != null) {
return objectMapper.readValue(body.string(), ImageSigningStatus.class);
}
throw new IOException("Empty response body");
}
}
// Close the client
public void close() {
if (httpClient != null) {
httpClient.dispatcher().executorService().shutdown();
httpClient.connectionPool().evictAll();
}
}
// Authentication interceptor
private static class AuthInterceptor implements Interceptor {
private final String authToken;
public AuthInterceptor(String authToken) {
this.authToken = authToken;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Request authenticatedRequest = originalRequest.newBuilder()
.header("Authorization", authToken)
.header("Content-Type", "application/json")
.build();
return chain.proceed(authenticatedRequest);
}
}
}

Data Models

package com.example.quay.security;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.Map;
// Repository models
@JsonIgnoreProperties(ignoreUnknown = true)
class RepositoryList {
@JsonProperty("repositories")
private List<Repository> repositories;
public List<Repository> getRepositories() { return repositories; }
public void setRepositories(List<Repository> repositories) { this.repositories = repositories; }
}
@JsonIgnoreProperties(ignoreUnknown = true)
class Repository {
@JsonProperty("namespace")
private String namespace;
@JsonProperty("name")
private String name;
@JsonProperty("description")
private String description;
@JsonProperty("is_public")
private boolean isPublic;
@JsonProperty("last_modified")
private long lastModified;
// Getters and setters
public String getNamespace() { return namespace; }
public void setNamespace(String namespace) { this.namespace = namespace; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public boolean isPublic() { return isPublic; }
public void setPublic(boolean isPublic) { this.isPublic = isPublic; }
public long getLastModified() { return lastModified; }
public void setLastModified(long lastModified) { this.lastModified = lastModified; }
}
@JsonIgnoreProperties(ignoreUnknown = true)
class RepositoryDetail {
@JsonProperty("namespace")
private String namespace;
@JsonProperty("name")
private String name;
@JsonProperty("description")
private String description;
@JsonProperty("is_public")
private boolean isPublic;
@JsonProperty("is_organization")
private boolean isOrganization;
@JsonProperty("tags")
private Map<String, TagInfo> tags;
@JsonProperty("security_scanning_enabled")
private boolean securityScanningEnabled;
// Getters and setters
public String getNamespace() { return namespace; }
public void setNamespace(String namespace) { this.namespace = namespace; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public boolean isPublic() { return isPublic; }
public void setPublic(boolean isPublic) { this.isPublic = isPublic; }
public boolean isOrganization() { return isOrganization; }
public void setOrganization(boolean isOrganization) { this.isOrganization = isOrganization; }
public Map<String, TagInfo> getTags() { return tags; }
public void setTags(Map<String, TagInfo> tags) { this.tags = tags; }
public boolean isSecurityScanningEnabled() { return securityScanningEnabled; }
public void setSecurityScanningEnabled(boolean securityScanningEnabled) { 
this.securityScanningEnabled = securityScanningEnabled; 
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
class TagInfo {
@JsonProperty("name")
private String name;
@JsonProperty("manifest_digest")
private String manifestDigest;
@JsonProperty("size")
private long size;
@JsonProperty("last_modified")
private String lastModified;
@JsonProperty("is_manifest_list")
private boolean isManifestList;
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getManifestDigest() { return manifestDigest; }
public void setManifestDigest(String manifestDigest) { this.manifestDigest = manifestDigest; }
public long getSize() { return size; }
public void setSize(long size) { this.size = size; }
public String getLastModified() { return lastModified; }
public void setLastModified(String lastModified) { this.lastModified = lastModified; }
public boolean isManifestList() { return isManifestList; }
public void setManifestList(boolean isManifestList) { this.isManifestList = isManifestList; }
}
// Security scanning models
@JsonIgnoreProperties(ignoreUnknown = true)
class SecurityScanResult {
@JsonProperty("status")
private String status;
@JsonProperty("data")
private SecurityScanData data;
@JsonProperty("scanned_by")
private String scannedBy;
@JsonProperty("scanned_date")
private String scannedDate;
// Getters and setters
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public SecurityScanData getData() { return data; }
public void setData(SecurityScanData data) { this.data = data; }
public String getScannedBy() { return scannedBy; }
public void setScannedBy(String scannedBy) { this.scannedBy = scannedBy; }
public String getScannedDate() { return scannedDate; }
public void setScannedDate(String scannedDate) { this.scannedDate = scannedDate; }
}
@JsonIgnoreProperties(ignoreUnknown = true)
class SecurityScanData {
@JsonProperty("Layer")
private Layer layer;
@JsonProperty("Metadata")
private ScanMetadata metadata;
// Getters and setters
public Layer getLayer() { return layer; }
public void setLayer(Layer layer) { this.layer = layer; }
public ScanMetadata getMetadata() { return metadata; }
public void setMetadata(ScanMetadata metadata) { this.metadata = metadata; }
}
@JsonIgnoreProperties(ignoreUnknown = true)
class Layer {
@JsonProperty("Name")
private String name;
@JsonProperty("ParentName")
private String parentName;
@JsonProperty("NamespaceName")
private String namespaceName;
@JsonProperty("IndexedByVersion")
private int indexedByVersion;
@JsonProperty("Features")
private List<Feature> features;
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getParentName() { return parentName; }
public void setParentName(String parentName) { this.parentName = parentName; }
public String getNamespaceName() { return namespaceName; }
public void setNamespaceName(String namespaceName) { this.namespaceName = namespaceName; }
public int getIndexedByVersion() { return indexedByVersion; }
public void setIndexedByVersion(int indexedByVersion) { this.indexedByVersion = indexedByVersion; }
public List<Feature> getFeatures() { return features; }
public void setFeatures(List<Feature> features) { this.features = features; }
}
@JsonIgnoreProperties(ignoreUnknown = true)
class Feature {
@JsonProperty("Name")
private String name;
@JsonProperty("Version")
private String version;
@JsonProperty("VersionFormat")
private String versionFormat;
@JsonProperty("Vulnerabilities")
private List<Vulnerability> vulnerabilities;
@JsonProperty("AddedBy")
private String addedBy;
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
public String getVersionFormat() { return versionFormat; }
public void setVersionFormat(String versionFormat) { this.versionFormat = versionFormat; }
public List<Vulnerability> getVulnerabilities() { return vulnerabilities; }
public void setVulnerabilities(List<Vulnerability> vulnerabilities) { this.vulnerabilities = vulnerabilities; }
public String getAddedBy() { return addedBy; }
public void setAddedBy(String addedBy) { this.addedBy = addedBy; }
}
@JsonIgnoreProperties(ignoreUnknown = true)
class Vulnerability {
@JsonProperty("Name")
private String name;
@JsonProperty("NamespaceName")
private String namespaceName;
@JsonProperty("Description")
private String description;
@JsonProperty("Link")
private String link;
@JsonProperty("Severity")
private String severity;
@JsonProperty("Metadata")
private VulnerabilityMetadata metadata;
@JsonProperty("FixedBy")
private String fixedBy;
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getNamespaceName() { return namespaceName; }
public void setNamespaceName(String namespaceName) { this.namespaceName = namespaceName; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getLink() { return link; }
public void setLink(String link) { this.link = link; }
public String getSeverity() { return severity; }
public void setSeverity(String severity) { this.severity = severity; }
public VulnerabilityMetadata getMetadata() { return metadata; }
public void setMetadata(VulnerabilityMetadata metadata) { this.metadata = metadata; }
public String getFixedBy() { return fixedBy; }
public void setFixedBy(String fixedBy) { this.fixedBy = fixedBy; }
}
@JsonIgnoreProperties(ignoreUnknown = true)
class VulnerabilityMetadata {
@JsonProperty("UpdatedBy")
private String updatedBy;
@JsonProperty("RepoName")
private String repoName;
@JsonProperty("RepoLink")
private String repoLink;
@JsonProperty("DistroName")
private String distroName;
@JsonProperty("DistroVersion")
private String distroVersion;
@JsonProperty("NVD")
private NVDMetadata nvd;
// Getters and setters
public String getUpdatedBy() { return updatedBy; }
public void setUpdatedBy(String updatedBy) { this.updatedBy = updatedBy; }
public String getRepoName() { return repoName; }
public void setRepoName(String repoName) { this.repoName = repoName; }
public String getRepoLink() { return repoLink; }
public void setRepoLink(String repoLink) { this.repoLink = repoLink; }
public String getDistroName() { return distroName; }
public void setDistroName(String distroName) { this.distroName = distroName; }
public String getDistroVersion() { return distroVersion; }
public void setDistroVersion(String distroVersion) { this.distroVersion = distroVersion; }
public NVDMetadata getNvd() { return nvd; }
public void setNvd(NVDMetadata nvd) { this.nvd = nvd; }
}
@JsonIgnoreProperties(ignoreUnknown = true)
class NVDMetadata {
@JsonProperty("CVSSv2")
private CVSSv2 cvssv2;
@JsonProperty("CVSSv3")
private CVSSv3 cvssv3;
// Getters and setters
public CVSSv2 getCvssv2() { return cvssv2; }
public void setCvssv2(CVSSv2 cvssv2) { this.cvssv2 = cvssv2; }
public CVSSv3 getCvssv3() { return cvssv3; }
public void setCvssv3(CVSSv3 cvssv3) { this.cvssv3 = cvssv3; }
}
@JsonIgnoreProperties(ignoreUnknown = true)
class CVSSv2 {
@JsonProperty("Score")
private double score;
@JsonProperty("Vectors")
private String vectors;
// Getters and setters
public double getScore() { return score; }
public void setScore(double score) { this.score = score; }
public String getVectors() { return vectors; }
public void setVectors(String vectors) { this.vectors = vectors; }
}
@JsonIgnoreProperties(ignoreUnknown = true)
class CVSSv3 {
@JsonProperty("Score")
private double score;
@JsonProperty("Vectors")
private String vectors;
// Getters and setters
public double getScore() { return score; }
public void setScore(double score) { this.score = score; }
public String getVectors() { return vectors; }
public void setVectors(String vectors) { this.vectors = vectors; }
}
@JsonIgnoreProperties(ignoreUnknown = true)
class ScanMetadata {
@JsonProperty("ScannerVersion")
private String scannerVersion;
@JsonProperty("ScanStatus")
private String scanStatus;
@JsonProperty("ReportStatus")
private String reportStatus;
// Getters and setters
public String getScannerVersion() { return scannerVersion; }
public void setScannerVersion(String scannerVersion) { this.scannerVersion = scannerVersion; }
public String getScanStatus() { return scanStatus; }
public void setScanStatus(String scanStatus) { this.scannerVersion = scanStatus; }
public String getReportStatus() { return reportStatus; }
public void setReportStatus(String reportStatus) { this.reportStatus = reportStatus; }
}
// Image signing models
@JsonIgnoreProperties(ignoreUnknown = true)
class ImageSigningStatus {
@JsonProperty("signatures")
private List<Signature> signatures;
@JsonProperty("is_signed")
private boolean isSigned;
// Getters and setters
public List<Signature> getSignatures() { return signatures; }
public void setSignatures(List<Signature> signatures) { this.signatures = signatures; }
public boolean isSigned() { return isSigned; }
public void setSigned(boolean isSigned) { this.isSigned = isSigned; }
}
@JsonIgnoreProperties(ignoreUnknown = true)
class Signature {
@JsonProperty("signer")
private String signer;
@JsonProperty("timestamp")
private String timestamp;
@JsonProperty("method")
private String method;
// Getters and setters
public String getSigner() { return signer; }
public void setSigner(String signer) { this.signer = signer; }
public String getTimestamp() { return timestamp; }
public void setTimestamp(String timestamp) { this.timestamp = timestamp; }
public String getMethod() { return method; }
public void setMethod(String method) { this.method = method; }
}

Security Scanner Implementation

Vulnerability Analysis Service

package com.example.quay.security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.stream.Collectors;
public class VulnerabilityAnalyzer {
private static final Logger logger = LoggerFactory.getLogger(VulnerabilityAnalyzer.class);
public VulnerabilityReport analyzeSecurityScan(String namespace, String repository, 
String tag, SecurityScanResult scanResult) {
logger.info("Analyzing security scan for {}/{}/{}", namespace, repository, tag);
VulnerabilityReport report = new VulnerabilityReport(namespace, repository, tag);
if (scanResult == null || scanResult.getData() == null) {
report.setStatus("NO_SCAN_DATA");
return report;
}
if (!"scanned".equals(scanResult.getStatus())) {
report.setStatus(scanResult.getStatus().toUpperCase());
return report;
}
report.setStatus("SCANNED");
report.setScannedBy(scanResult.getScannedBy());
report.setScannedDate(scanResult.getScannedDate());
// Extract vulnerabilities from scan result
extractVulnerabilities(scanResult, report);
// Calculate summary
calculateSummary(report);
logger.info("Analysis complete: {} vulnerabilities found", report.getTotalVulnerabilities());
return report;
}
private void extractVulnerabilities(SecurityScanResult scanResult, VulnerabilityReport report) {
SecurityScanData data = scanResult.getData();
if (data == null || data.getLayer() == null) {
return;
}
Layer layer = data.getLayer();
if (layer.getFeatures() == null) {
return;
}
for (Feature feature : layer.getFeatures()) {
if (feature.getVulnerabilities() != null) {
for (Vulnerability vulnerability : feature.getVulnerabilities()) {
VulnerabilityDetail vulnDetail = createVulnerabilityDetail(feature, vulnerability);
report.addVulnerability(vulnDetail);
}
}
}
}
private VulnerabilityDetail createVulnerabilityDetail(Feature feature, Vulnerability vulnerability) {
VulnerabilityDetail detail = new VulnerabilityDetail();
detail.setId(vulnerability.getName());
detail.setPackageName(feature.getName());
detail.setPackageVersion(feature.getVersion());
detail.setSeverity(vulnerability.getSeverity());
detail.setDescription(vulnerability.getDescription());
detail.setLink(vulnerability.getLink());
detail.setFixedBy(vulnerability.getFixedBy());
detail.setAddedBy(feature.getAddedBy());
// Extract CVSS scores
if (vulnerability.getMetadata() != null && vulnerability.getMetadata().getNvd() != null) {
NVDMetadata nvd = vulnerability.getMetadata().getNvd();
if (nvd.getCvssv2() != null) {
detail.setCvssV2Score(nvd.getCvssv2().getScore());
}
if (nvd.getCvssv3() != null) {
detail.setCvssV3Score(nvd.getCvssv3().getScore());
}
}
return detail;
}
private void calculateSummary(VulnerabilityReport report) {
Map<String, Integer> severityCount = new HashMap<>();
int totalVulnerabilities = 0;
for (VulnerabilityDetail vuln : report.getVulnerabilities()) {
String severity = vuln.getSeverity();
severityCount.put(severity, severityCount.getOrDefault(severity, 0) + 1);
totalVulnerabilities++;
}
report.setVulnerabilitySummary(severityCount);
report.setTotalVulnerabilities(totalVulnerabilities);
// Calculate risk score (weighted by severity)
double riskScore = calculateRiskScore(severityCount);
report.setRiskScore(riskScore);
// Determine overall status
String overallStatus = determineOverallStatus(severityCount, riskScore);
report.setOverallStatus(overallStatus);
}
private double calculateRiskScore(Map<String, Integer> severityCount) {
double score = 0.0;
// Weighted scoring
score += severityCount.getOrDefault("Critical", 0) * 10.0;
score += severityCount.getOrDefault("High", 0) * 7.5;
score += severityCount.getOrDefault("Medium", 0) * 5.0;
score += severityCount.getOrDefault("Low", 0) * 2.5;
score += severityCount.getOrDefault("Negligible", 0) * 1.0;
return score;
}
private String determineOverallStatus(Map<String, Integer> severityCount, double riskScore) {
if (severityCount.getOrDefault("Critical", 0) > 0) {
return "CRITICAL";
} else if (severityCount.getOrDefault("High", 0) > 0) {
return "HIGH";
} else if (riskScore > 20.0) {
return "MEDIUM";
} else if (riskScore > 5.0) {
return "LOW";
} else {
return "CLEAN";
}
}
public List<VulnerabilityDetail> getCriticalVulnerabilities(VulnerabilityReport report) {
return report.getVulnerabilities().stream()
.filter(vuln -> "Critical".equalsIgnoreCase(vuln.getSeverity()))
.collect(Collectors.toList());
}
public List<VulnerabilityDetail> getFixableVulnerabilities(VulnerabilityReport report) {
return report.getVulnerabilities().stream()
.filter(vuln -> vuln.getFixedBy() != null && !vuln.getFixedBy().isEmpty())
.collect(Collectors.toList());
}
public Map<String, List<VulnerabilityDetail>> groupByPackage(VulnerabilityReport report) {
return report.getVulnerabilities().stream()
.collect(Collectors.groupingBy(VulnerabilityDetail::getPackageName));
}
public SecurityCompliance checkCompliance(VulnerabilityReport report, SecurityPolicy policy) {
SecurityCompliance compliance = new SecurityCompliance();
compliance.setReport(report);
compliance.setPolicy(policy);
// Check critical vulnerabilities
List<VulnerabilityDetail> criticalVulns = getCriticalVulnerabilities(report);
if (!criticalVulns.isEmpty() && !policy.isAllowCritical()) {
compliance.setCompliant(false);
compliance.getViolations().add("Critical vulnerabilities found: " + criticalVulns.size());
}
// Check high vulnerabilities
List<VulnerabilityDetail> highVulns = report.getVulnerabilities().stream()
.filter(vuln -> "High".equalsIgnoreCase(vuln.getSeverity()))
.collect(Collectors.toList());
if (highVulns.size() > policy.getMaxHighVulnerabilities()) {
compliance.setCompliant(false);
compliance.getViolations().add("High vulnerabilities exceed limit: " + 
highVulns.size() + " > " + policy.getMaxHighVulnerabilities());
}
// Check risk score
if (report.getRiskScore() > policy.getMaxRiskScore()) {
compliance.setCompliant(false);
compliance.getViolations().add("Risk score exceeds limit: " + 
report.getRiskScore() + " > " + policy.getMaxRiskScore());
}
if (compliance.getViolations().isEmpty()) {
compliance.setCompliant(true);
}
return compliance;
}
}
// Enhanced report models
class VulnerabilityReport {
private final String namespace;
private final String repository;
private final String tag;
private String status;
private String scannedBy;
private String scannedDate;
private String overallStatus;
private double riskScore;
private int totalVulnerabilities;
private Map<String, Integer> vulnerabilitySummary;
private List<VulnerabilityDetail> vulnerabilities;
public VulnerabilityReport(String namespace, String repository, String tag) {
this.namespace = namespace;
this.repository = repository;
this.tag = tag;
this.vulnerabilities = new ArrayList<>();
this.vulnerabilitySummary = new HashMap<>();
}
public void addVulnerability(VulnerabilityDetail vulnerability) {
vulnerabilities.add(vulnerability);
}
// Getters and setters
public String getNamespace() { return namespace; }
public String getRepository() { return repository; }
public String getTag() { return tag; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getScannedBy() { return scannedBy; }
public void setScannedBy(String scannedBy) { this.scannedBy = scannedBy; }
public String getScannedDate() { return scannedDate; }
public void setScannedDate(String scannedDate) { this.scannedDate = scannedDate; }
public String getOverallStatus() { return overallStatus; }
public void setOverallStatus(String overallStatus) { this.overallStatus = overallStatus; }
public double getRiskScore() { return riskScore; }
public void setRiskScore(double riskScore) { this.riskScore = riskScore; }
public int getTotalVulnerabilities() { return totalVulnerabilities; }
public void setTotalVulnerabilities(int totalVulnerabilities) { this.totalVulnerabilities = totalVulnerabilities; }
public Map<String, Integer> getVulnerabilitySummary() { return vulnerabilitySummary; }
public void setVulnerabilitySummary(Map<String, Integer> vulnerabilitySummary) { this.vulnerabilitySummary = vulnerabilitySummary; }
public List<VulnerabilityDetail> getVulnerabilities() { return vulnerabilities; }
public void setVulnerabilities(List<VulnerabilityDetail> vulnerabilities) { this.vulnerabilities = vulnerabilities; }
public String getImageReference() {
return namespace + "/" + repository + ":" + tag;
}
}
class VulnerabilityDetail {
private String id;
private String packageName;
private String packageVersion;
private String severity;
private String description;
private String link;
private String fixedBy;
private String addedBy;
private Double cvssV2Score;
private Double cvssV3Score;
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getPackageName() { return packageName; }
public void setPackageName(String packageName) { this.packageName = packageName; }
public String getPackageVersion() { return packageVersion; }
public void setPackageVersion(String packageVersion) { this.packageVersion = packageVersion; }
public String getSeverity() { return severity; }
public void setSeverity(String severity) { this.severity = severity; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getLink() { return link; }
public void setLink(String link) { this.link = link; }
public String getFixedBy() { return fixedBy; }
public void setFixedBy(String fixedBy) { this.fixedBy = fixedBy; }
public String getAddedBy() { return addedBy; }
public void setAddedBy(String addedBy) { this.addedBy = addedBy; }
public Double getCvssV2Score() { return cvssV2Score; }
public void setCvssV2Score(Double cvssV2Score) { this.cvssV2Score = cvssV2Score; }
public Double getCvssV3Score() { return cvssV3Score; }
public void setCvssV3Score(Double cvssV3Score) { this.cvssV3Score = cvssV3Score; }
public boolean isFixable() {
return fixedBy != null && !fixedBy.isEmpty();
}
}
class SecurityPolicy {
private boolean allowCritical = false;
private int maxHighVulnerabilities = 0;
private int maxMediumVulnerabilities = 10;
private int maxLowVulnerabilities = 50;
private double maxRiskScore = 25.0;
private boolean requireSigned = false;
private List<String> allowedLicenses = Arrays.asList("Apache-2.0", "MIT", "BSD-3-Clause");
private List<String> bannedPackages = new ArrayList<>();
// Getters and setters
public boolean isAllowCritical() { return allowCritical; }
public void setAllowCritical(boolean allowCritical) { this.allowCritical = allowCritical; }
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 double getMaxRiskScore() { return maxRiskScore; }
public void setMaxRiskScore(double maxRiskScore) { this.maxRiskScore = maxRiskScore; }
public boolean isRequireSigned() { return requireSigned; }
public void setRequireSigned(boolean requireSigned) { this.requireSigned = requireSigned; }
public List<String> getAllowedLicenses() { return allowedLicenses; }
public void setAllowedLicenses(List<String> allowedLicenses) { this.allowedLicenses = allowedLicenses; }
public List<String> getBannedPackages() { return bannedPackages; }
public void setBannedPackages(List<String> bannedPackages) { this.bannedPackages = bannedPackages; }
}
class SecurityCompliance {
private VulnerabilityReport report;
private SecurityPolicy policy;
private boolean isCompliant;
private List<String> violations;
public SecurityCompliance() {
this.violations = new ArrayList<>();
}
// Getters and setters
public VulnerabilityReport getReport() { return report; }
public void setReport(VulnerabilityReport report) { this.report = report; }
public SecurityPolicy getPolicy() { return policy; }
public void setPolicy(SecurityPolicy policy) { this.policy = policy; }
public boolean isCompliant() { return isCompliant; }
public void setCompliant(boolean isCompliant) { this.isCompliant = isCompliant; }
public List<String> getViolations() { return violations; }
public void setViolations(List<String> violations) { this.violations = violations; }
}

Image Security Manager

package com.example.quay.security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.*;
public class ImageSecurityManager {
private static final Logger logger = LoggerFactory.getLogger(ImageSecurityManager.class);
private final QuayClient quayClient;
private final VulnerabilityAnalyzer vulnerabilityAnalyzer;
public ImageSecurityManager(QuayClient quayClient, VulnerabilityAnalyzer vulnerabilityAnalyzer) {
this.quayClient = quayClient;
this.vulnerabilityAnalyzer = vulnerabilityAnalyzer;
}
public SecurityAssessment assessImage(String namespace, String repository, String tag) 
throws IOException {
logger.info("Assessing security for {}/{}/{}", namespace, repository, tag);
SecurityAssessment assessment = new SecurityAssessment(namespace, repository, tag);
// Get repository details
RepositoryDetail repoDetail = quayClient.getRepository(namespace, repository);
assessment.setRepositoryDetail(repoDetail);
// Get security scan results
SecurityScanResult scanResult = quayClient.getSecurityScan(namespace, repository, tag);
VulnerabilityReport vulnReport = vulnerabilityAnalyzer.analyzeSecurityScan(
namespace, repository, tag, scanResult);
assessment.setVulnerabilityReport(vulnReport);
// Get signing status
ImageSigningStatus signingStatus = quayClient.getSigningStatus(namespace, repository, tag);
assessment.setSigningStatus(signingStatus);
// Calculate overall security score
calculateSecurityScore(assessment);
logger.info("Security assessment complete for {}/{}/{} - Score: {}", 
namespace, repository, tag, assessment.getSecurityScore());
return assessment;
}
public List<SecurityAssessment> assessRepository(String namespace, String repository) 
throws IOException {
logger.info("Assessing security for repository {}/{}", namespace, repository);
RepositoryDetail repoDetail = quayClient.getRepository(namespace, repository);
List<SecurityAssessment> assessments = new ArrayList<>();
if (repoDetail.getTags() != null) {
for (String tag : repoDetail.getTags().keySet()) {
try {
SecurityAssessment assessment = assessImage(namespace, repository, tag);
assessments.add(assessment);
} catch (Exception e) {
logger.warn("Failed to assess image {}/{}:{}", namespace, repository, tag, e);
}
}
}
return assessments;
}
public SecurityCompliance checkCompliance(String namespace, String repository, String tag,
SecurityPolicy policy) throws IOException {
SecurityAssessment assessment = assessImage(namespace, repository, tag);
return vulnerabilityAnalyzer.checkCompliance(assessment.getVulnerabilityReport(), policy);
}
public ComplianceReport checkRepositoryCompliance(String namespace, String repository,
SecurityPolicy policy) throws IOException {
List<SecurityAssessment> assessments = assessRepository(namespace, repository);
ComplianceReport complianceReport = new ComplianceReport(namespace, repository, policy);
for (SecurityAssessment assessment : assessments) {
SecurityCompliance compliance = vulnerabilityAnalyzer.checkCompliance(
assessment.getVulnerabilityReport(), policy);
complianceReport.addImageCompliance(assessment.getTag(), compliance);
}
complianceReport.calculateSummary();
return complianceReport;
}
public void triggerSecurityScan(String namespace, String repository, String tag) 
throws IOException {
logger.info("Triggering security scan for {}/{}/{}", namespace, repository, tag);
quayClient.triggerSecurityScan(namespace, repository, tag);
}
public List<String> findVulnerableImages(String namespace, String severity) throws IOException {
logger.info("Finding images with {} vulnerabilities in namespace {}", severity, namespace);
RepositoryList repos = quayClient.listRepositories(namespace);
List<String> vulnerableImages = new ArrayList<>();
for (Repository repo : repos.getRepositories()) {
try {
RepositoryDetail repoDetail = quayClient.getRepository(
repo.getNamespace(), repo.getName());
if (repoDetail.getTags() != null) {
for (String tag : repoDetail.getTags().keySet()) {
try {
SecurityScanResult scanResult = quayClient.getSecurityScan(
repo.getNamespace(), repo.getName(), tag);
if (scanResult != null && "scanned".equals(scanResult.getStatus())) {
VulnerabilityReport report = vulnerabilityAnalyzer.analyzeSecurityScan(
repo.getNamespace(), repo.getName(), tag, scanResult);
if (hasSeverityVulnerabilities(report, severity)) {
vulnerableImages.add(repo.getNamespace() + "/" + 
repo.getName() + ":" + tag);
}
}
} catch (Exception e) {
logger.debug("Skipping image {}/{}:{}", 
repo.getNamespace(), repo.getName(), tag, e);
}
}
}
} catch (Exception e) {
logger.warn("Failed to process repository {}/{}", 
repo.getNamespace(), repo.getName(), e);
}
}
return vulnerableImages;
}
private boolean hasSeverityVulnerabilities(VulnerabilityReport report, String severity) {
if (report.getVulnerabilitySummary() == null) return false;
return report.getVulnerabilitySummary().getOrDefault(severity, 0) > 0;
}
private void calculateSecurityScore(SecurityAssessment assessment) {
double score = 100.0; // Start with perfect score
VulnerabilityReport vulnReport = assessment.getVulnerabilityReport();
ImageSigningStatus signingStatus = assessment.getSigningStatus();
// Deduct for vulnerabilities
if (vulnReport.getRiskScore() > 0) {
score -= Math.min(vulnReport.getRiskScore() * 2, 60.0);
}
// Deduct for unsigned images
if (signingStatus != null && !signingStatus.isSigned()) {
score -= 10.0;
}
// Bonus for no vulnerabilities
if (vulnReport.getTotalVulnerabilities() == 0) {
score += 5.0;
}
// Ensure score is within bounds
score = Math.max(0.0, Math.min(100.0, score));
assessment.setSecurityScore(score);
// Set security level
if (score >= 90.0) assessment.setSecurityLevel("EXCELLENT");
else if (score >= 75.0) assessment.setSecurityLevel("GOOD");
else if (score >= 60.0) assessment.setSecurityLevel("FAIR");
else if (score >= 40.0) assessment.setSecurityLevel("POOR");
else assessment.setSecurityLevel("CRITICAL");
}
}
// Assessment models
class SecurityAssessment {
private final String namespace;
private final String repository;
private final String tag;
private RepositoryDetail repositoryDetail;
private VulnerabilityReport vulnerabilityReport;
private ImageSigningStatus signingStatus;
private double securityScore;
private String securityLevel;
private Date assessmentDate;
public SecurityAssessment(String namespace, String repository, String tag) {
this.namespace = namespace;
this.repository = repository;
this.tag = tag;
this.assessmentDate = new Date();
}
// Getters and setters
public String getNamespace() { return namespace; }
public String getRepository() { return repository; }
public String getTag() { return tag; }
public RepositoryDetail getRepositoryDetail() { return repositoryDetail; }
public void setRepositoryDetail(RepositoryDetail repositoryDetail) { this.repositoryDetail = repositoryDetail; }
public VulnerabilityReport getVulnerabilityReport() { return vulnerabilityReport; }
public void setVulnerabilityReport(VulnerabilityReport vulnerabilityReport) { this.vulnerabilityReport = vulnerabilityReport; }
public ImageSigningStatus getSigningStatus() { return signingStatus; }
public void setSigningStatus(ImageSigningStatus signingStatus) { this.signingStatus = signingStatus; }
public double getSecurityScore() { return securityScore; }
public void setSecurityScore(double securityScore) { this.securityScore = securityScore; }
public String getSecurityLevel() { return securityLevel; }
public void setSecurityLevel(String securityLevel) { this.securityLevel = securityLevel; }
public Date getAssessmentDate() { return assessmentDate; }
public void setAssessmentDate(Date assessmentDate) { this.assessmentDate = assessmentDate; }
public String getImageReference() {
return namespace + "/" + repository + ":" + tag;
}
}
class ComplianceReport {
private final String namespace;
private final String repository;
private final SecurityPolicy policy;
private final Map<String, SecurityCompliance> imageCompliance;
private int totalImages;
private int compliantImages;
private int nonCompliantImages;
private Date reportDate;
public ComplianceReport(String namespace, String repository, SecurityPolicy policy) {
this.namespace = namespace;
this.repository = repository;
this.policy = policy;
this.imageCompliance = new HashMap<>();
this.reportDate = new Date();
}
public void addImageCompliance(String tag, SecurityCompliance compliance) {
imageCompliance.put(tag, compliance);
}
public void calculateSummary() {
totalImages = imageCompliance.size();
compliantImages = (int) imageCompliance.values().stream()
.filter(SecurityCompliance::isCompliant)
.count();
nonCompliantImages = totalImages - compliantImages;
}
// Getters
public String getNamespace() { return namespace; }
public String getRepository() { return repository; }
public SecurityPolicy getPolicy() { return policy; }
public Map<String, SecurityCompliance> getImageCompliance() { return imageCompliance; }
public int getTotalImages() { return totalImages; }
public int getCompliantImages() { return compliantImages; }
public int getNonCompliantImages() { return nonCompliantImages; }
public Date getReportDate() { return reportDate; }
public double getCompliancePercentage() {
return totalImages > 0 ? (compliantImages * 100.0) / totalImages : 0.0;
}
}

Report Generation

package com.example.quay.report;
import com.example.quay.security.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class SecurityReportGenerator {
private static final Logger logger = LoggerFactory.getLogger(SecurityReportGenerator.class);
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
private final ObjectMapper objectMapper;
public SecurityReportGenerator() {
this.objectMapper = new ObjectMapper();
this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
}
public void generateHtmlReport(SecurityAssessment assessment, Path outputDir) throws IOException {
String timestamp = DATE_FORMAT.format(new Date());
String filename = "quay_security_report_" + timestamp + ".html";
Path outputPath = outputDir.resolve(filename);
String htmlContent = generateHtmlContent(assessment);
try (FileWriter writer = new FileWriter(outputPath.toFile())) {
writer.write(htmlContent);
}
logger.info("HTML security report generated: {}", outputPath);
}
public void generateHtmlReport(ComplianceReport complianceReport, Path outputDir) throws IOException {
String timestamp = DATE_FORMAT.format(new Date());
String filename = "quay_compliance_report_" + timestamp + ".html";
Path outputPath = outputDir.resolve(filename);
String htmlContent = generateComplianceHtmlContent(complianceReport);
try (FileWriter writer = new FileWriter(outputPath.toFile())) {
writer.write(htmlContent);
}
logger.info("HTML compliance report generated: {}", outputPath);
}
public void generateJsonReport(SecurityAssessment assessment, Path outputDir) throws IOException {
String timestamp = DATE_FORMAT.format(new Date());
String filename = "quay_security_report_" + timestamp + ".json";
Path outputPath = outputDir.resolve(filename);
objectMapper.writeValue(outputPath.toFile(), assessment);
logger.info("JSON security report generated: {}", outputPath);
}
public void generateConsoleReport(SecurityAssessment assessment) {
System.out.println("=== QUAY SECURITY ASSESSMENT ===");
System.out.printf("Image: %s%n", assessment.getImageReference());
System.out.printf("Security Score: %.1f/100 (%s)%n", 
assessment.getSecurityScore(), assessment.getSecurityLevel());
System.out.printf("Assessment Date: %s%n", assessment.getAssessmentDate());
System.out.println();
VulnerabilityReport vulnReport = assessment.getVulnerabilityReport();
System.out.println("VULNERABILITY SUMMARY:");
Map<String, Integer> summary = vulnReport.getVulnerabilitySummary();
for (Map.Entry<String, Integer> entry : summary.entrySet()) {
System.out.printf("  %s: %d%n", entry.getKey(), entry.getValue());
}
System.out.printf("Total Vulnerabilities: %d%n", vulnReport.getTotalVulnerabilities());
System.out.printf("Risk Score: %.1f%n", vulnReport.getRiskScore());
System.out.println();
// Critical vulnerabilities
List<VulnerabilityDetail> criticalVulns = vulnReport.getVulnerabilities().stream()
.filter(v -> "Critical".equalsIgnoreCase(v.getSeverity()))
.toList();
if (!criticalVulns.isEmpty()) {
System.out.println("CRITICAL VULNERABILITIES:");
for (VulnerabilityDetail vuln : criticalVulns) {
System.out.printf("  %s - %s%n", vuln.getId(), vuln.getPackageName());
System.out.printf("    Fixed in: %s%n", vuln.getFixedBy() != null ? vuln.getFixedBy() : "Not available");
}
System.out.println();
}
// Signing status
ImageSigningStatus signingStatus = assessment.getSigningStatus();
System.out.printf("Image Signed: %s%n", signingStatus != null && signingStatus.isSigned() ? "YES" : "NO");
if (signingStatus != null && signingStatus.isSigned()) {
System.out.printf("Signatures: %d%n", signingStatus.getSignatures().size());
}
}
private String generateHtmlContent(SecurityAssessment assessment) {
VulnerabilityReport vulnReport = assessment.getVulnerabilityReport();
ImageSigningStatus signingStatus = assessment.getSigningStatus();
return """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quay Security Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background: #2c3e50; color: white; padding: 20px; border-radius: 5px; }
.summary { margin: 20px 0; }
.section { margin: 20px 0; border: 1px solid #ddd; padding: 15px; border-radius: 5px; }
.critical { color: #e74c3c; font-weight: bold; }
.high { color: #e67e22; }
.medium { color: #f39c12; }
.low { color: #27ae60; }
.security-score { font-size: 2em; font-weight: bold; margin: 10px 0; }
.score-excellent { color: #27ae60; }
.score-good { color: #f39c12; }
.score-poor { color: #e74c3c; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<div class="header">
<h1>Quay Security Assessment Report</h1>
<p>Image: """ + assessment.getImageReference() + """</p>
<p>Assessment Date: """ + assessment.getAssessmentDate() + """</p>
</div>
<div class="summary">
<div class="security-score """ + getScoreClass(assessment.getSecurityScore()) + """">
Security Score: """ + String.format("%.1f", assessment.getSecurityScore()) + """/100
</div>
<p>Security Level: """ + assessment.getSecurityLevel() + """</p>
</div>
<div class="section">
<h2>Vulnerability Summary</h2>
<table>
<tr><th>Severity</th><th>Count</th></tr>
""" + generateVulnerabilitySummaryRows(vulnReport) + """
</table>
<p>Total Vulnerabilities: """ + vulnReport.getTotalVulnerabilities() + """</p>
<p>Risk Score: """ + String.format("%.1f", vulnReport.getRiskScore()) + """</p>
</div>
<div class="section">
<h2>Image Signing</h2>
<p>Signed: """ + (signingStatus != null && signingStatus.isSigned() ? "YES" : "NO") + """</p>
""" + (signingStatus != null && signingStatus.isSigned() ? 
"<p>Signatures: " + signingStatus.getSignatures().size() + "</p>" : "") + """
</div>
<div class="section">
<h2>Critical Vulnerabilities</h2>
""" + generateCriticalVulnerabilitiesList(vulnReport) + """
</div>
</body>
</html>
""";
}
private String generateComplianceHtmlContent(ComplianceReport complianceReport) {
return """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quay Compliance Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background: #2c3e50; color: white; padding: 20px; border-radius: 5px; }
.summary { margin: 20px 0; }
.section { margin: 20px 0; border: 1px solid #ddd; padding: 15px; border-radius: 5px; }
.compliant { color: #27ae60; }
.non-compliant { color: #e74c3c; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<div class="header">
<h1>Quay Compliance Report</h1>
<p>Repository: """ + complianceReport.getNamespace() + "/" + complianceReport.getRepository() + """</p>
<p>Report Date: """ + complianceReport.getReportDate() + """</p>
</div>
<div class="summary">
<h2>Compliance Summary</h2>
<p>Total Images: """ + complianceReport.getTotalImages() + """</p>
<p>Compliant Images: """ + complianceReport.getCompliantImages() + """</p>
<p>Non-compliant Images: """ + complianceReport.getNonCompliantImages() + """</p>
<p>Compliance Rate: """ + String.format("%.1f", complianceReport.getCompliancePercentage()) + """%</p>
</div>
<div class="section">
<h2>Image Compliance Status</h2>
<table>
<tr><th>Image Tag</th><th>Compliant</th><th>Violations</th></tr>
""" + generateComplianceRows(complianceReport) + """
</table>
</div>
</body>
</html>
""";
}
private String generateVulnerabilitySummaryRows(VulnerabilityReport vulnReport) {
StringBuilder rows = new StringBuilder();
Map<String, Integer> summary = vulnReport.getVulnerabilitySummary();
for (Map.Entry<String, Integer> entry : summary.entrySet()) {
String severityClass = getSeverityClass(entry.getKey());
rows.append(String.format("<tr><td class='%s'>%s</td><td>%d</td></tr>", 
severityClass, entry.getKey(), entry.getValue()));
}
return rows.toString();
}
private String generateCriticalVulnerabilitiesList(VulnerabilityReport vulnReport) {
List<VulnerabilityDetail> criticalVulns = vulnReport.getVulnerabilities().stream()
.filter(v -> "Critical".equalsIgnoreCase(v.getSeverity()))
.toList();
if (criticalVulns.isEmpty()) {
return "<p>No critical vulnerabilities found.</p>";
}
StringBuilder list = new StringBuilder("<ul>");
for (VulnerabilityDetail vuln : criticalVulns) {
list.append(String.format("<li><strong>%s</strong> - %s", vuln.getId(), vuln.getPackageName()));
list.append(String.format("<br>Fixed in: %s", 
vuln.getFixedBy() != null ? vuln.getFixedBy() : "Not available"));
list.append("</li>");
}
list.append("</ul>");
return list.toString();
}
private String generateComplianceRows(ComplianceReport complianceReport) {
StringBuilder rows = new StringBuilder();
for (Map.Entry<String, SecurityCompliance> entry : complianceReport.getImageCompliance().entrySet()) {
String tag = entry.getKey();
SecurityCompliance compliance = entry.getValue();
String statusClass = compliance.isCompliant() ? "compliant" : "non-compliant";
String statusText = compliance.isCompliant() ? "YES" : "NO";
String violations = String.join("; ", compliance.getViolations());
rows.append(String.format("<tr><td>%s</td><td class='%s'>%s</td><td>%s</td></tr>", 
tag, statusClass, statusText, violations));
}
return rows.toString();
}
private String getScoreClass(double score) {
if (score >= 80.0) return "score-excellent";
else if (score >= 60.0) return "score-good";
else return "score-poor";
}
private String getSeverityClass(String severity) {
if (severity == null) return "low";
return switch (severity.toLowerCase()) {
case "critical" -> "critical";
case "high" -> "high";
case "medium" -> "medium";
default -> "low";
};
}
}

Usage Example

package com.example.quay;
import com.example.quay.security.*;
import com.example.quay.report.SecurityReportGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
public class QuaySecurityExample {
private static final Logger logger = LoggerFactory.getLogger(QuaySecurityExample.class);
public static void main(String[] args) {
// Configuration
String quayUrl = "https://quay.io";
String authToken = "your-auth-token";
String namespace = "your-namespace";
String repository = "your-repository";
String tag = "latest";
Path reportOutputDir = Paths.get("security-reports");
QuayClient quayClient = null;
try {
// Initialize Quay client
quayClient = new QuayClient(quayUrl, authToken, 60);
if (!quayClient.testConnection()) {
logger.error("Failed to connect to Quay");
System.exit(1);
}
// Initialize services
VulnerabilityAnalyzer vulnerabilityAnalyzer = new VulnerabilityAnalyzer();
ImageSecurityManager securityManager = new ImageSecurityManager(quayClient, vulnerabilityAnalyzer);
SecurityReportGenerator reportGenerator = new SecurityReportGenerator();
// Assess image security
SecurityAssessment assessment = securityManager.assessImage(namespace, repository, tag);
// Generate reports
reportGenerator.generateConsoleReport(assessment);
reportGenerator.generateHtmlReport(assessment, reportOutputDir);
reportGenerator.generateJsonReport(assessment, reportOutputDir);
// Check compliance
SecurityPolicy policy = new SecurityPolicy();
policy.setAllowCritical(false);
policy.setMaxHighVulnerabilities(0);
policy.setMaxRiskScore(20.0);
SecurityCompliance compliance = securityManager.checkCompliance(
namespace, repository, tag, policy);
if (!compliance.isCompliant()) {
logger.warn("Image is not compliant with security policy:");
for (String violation : compliance.getViolations()) {
logger.warn("  - {}", violation);
}
System.exit(1);
}
logger.info("Security assessment completed successfully");
} catch (Exception e) {
logger.error("Quay security analysis failed", e);
System.exit(1);
} finally {
if (quayClient != null) {
quayClient.close();
}
}
}
}

GitHub Actions Integration

name: Quay Security Scan
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 0 * * 0'  # Weekly on Sunday
jobs:
quay-security-scan:
name: "Quay Security Scan"
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Run Quay Security Scan
run: |
mvn -B exec:java -Dexec.mainClass="com.example.quay.QuaySecurityExample" \
-Dexec.args="--namespace my-namespace --repository my-app --tag latest"
env:
QUAY_URL: ${{ secrets.QUAY_URL }}
QUAY_TOKEN: ${{ secrets.QUAY_TOKEN }}
- name: Upload Security Report
uses: actions/upload-artifact@v3
with:
name: quay-security-report
path: security-reports/
- name: Check for Critical Vulnerabilities
run: |
# Check if there are critical vulnerabilities
if grep -q "CRITICAL" security-reports/*.json; then
echo "Critical vulnerabilities found - failing build"
exit 1
fi

Conclusion

This comprehensive Quay Security implementation for Java provides:

  • Complete Quay API integration for container registry operations
  • Automated vulnerability scanning and analysis
  • Security policy enforcement with compliance checking
  • Image signing verification for integrity assurance
  • Comprehensive reporting in HTML, JSON, and console formats
  • CI/CD integration with GitHub Actions

Key benefits include:

  1. Container Security Management - Comprehensive security for container images
  2. Automated Vulnerability Detection - Continuous monitoring of CVEs
  3. Policy Enforcement - Automated compliance checking
  4. Image Integrity - Verification through digital signatures
  5. Actionable Reporting - Clear insights for development and security teams

The implementation follows industry best practices and provides a solid foundation for integrating Quay Security into Java development workflows for comprehensive container security management.

Leave a Reply

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


Macro Nepal Helper