Overview
Quay.io is a container registry with robust security features including vulnerability scanning, image signing, access control, and security policies. This guide covers integrating Quay.io security features into Java applications.
Core Security Features
- Vulnerability Scanning: Built-in security scanning
- Image Signing: Content trust and verification
- Access Control: RBAC and repository permissions
- Security Notifications: Webhooks and alerts
- Robot Accounts: Automated access with limited permissions
Quay.io API Integration
1. Quay.io API Client
package com.example.quay.security; import org.springframework.http.*; import org.springframework.web.client.RestTemplate; import org.springframework.stereotype.Component; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.*; @Component public class QuayApiClient { private final RestTemplate restTemplate; private final ObjectMapper objectMapper; private final String quayToken; private final String baseUrl; public QuayApiClient(@Value("${quay.token}") String quayToken, @Value("${quay.base-url:https://quay.io}") String baseUrl) { this.restTemplate = new RestTemplate(); this.objectMapper = new ObjectMapper(); this.quayToken = quayToken; this.baseUrl = baseUrl; } // Repository Security Operations public RepositorySecurityReport getRepositorySecurity(String namespace, String repository) { HttpHeaders headers = createHeaders(); String url = String.format("%s/api/v1/repository/%s/%s", baseUrl, namespace, repository); ResponseEntity<RepositorySecurityReport> response = restTemplate.exchange( url, HttpMethod.GET, new HttpEntity<>(headers), RepositorySecurityReport.class); return response.getBody(); } public List<ImageVulnerability> getImageVulnerabilities(String namespace, String repository, String tag) { HttpHeaders headers = createHeaders(); String url = String.format("%s/api/v1/repository/%s/%s/tag/%s/security", baseUrl, namespace, repository, tag); ResponseEntity<ImageSecurityReport> response = restTemplate.exchange( url, HttpMethod.GET, new HttpEntity<>(headers), ImageSecurityReport.class); return response.getBody() != null ? response.getBody().getVulnerabilities() : Collections.emptyList(); } public ScanStatus triggerImageScan(String namespace, String repository, String tag) { HttpHeaders headers = createHeaders(); String url = String.format("%s/api/v1/repository/%s/%s/tag/%s/scan", baseUrl, namespace, repository, tag); ResponseEntity<ScanStatus> response = restTemplate.exchange( url, HttpMethod.POST, new HttpEntity<>(headers), ScanStatus.class); return response.getBody(); } // Robot Account Management public RobotAccount createRobotAccount(String namespace, String robotName) { HttpHeaders headers = createHeaders(); String url = String.format("%s/api/v1/organization/%s/robots/%s", baseUrl, namespace, robotName); Map<String, Object> requestBody = new HashMap<>(); requestBody.put("description", "Robot account for automated deployments"); HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers); ResponseEntity<RobotAccount> response = restTemplate.exchange( url, HttpMethod.PUT, request, RobotAccount.class); return response.getBody(); } public void setRobotPermissions(String namespace, String robotName, String repository, String permission) { HttpHeaders headers = createHeaders(); String url = String.format("%s/api/v1/repository/%s/%s/permissions/user/%s", baseUrl, namespace, repository, robotName); Map<String, String> requestBody = new HashMap<>(); requestBody.put("role", permission); // read, write, admin HttpEntity<Map<String, String>> request = new HttpEntity<>(requestBody, headers); restTemplate.exchange(url, HttpMethod.PUT, request, Void.class); } // Security Notifications public void createSecurityNotification(String namespace, String repository, SecurityNotificationConfig config) { HttpHeaders headers = createHeaders(); String url = String.format("%s/api/v1/repository/%s/%s/notification/", baseUrl, namespace, repository); HttpEntity<SecurityNotificationConfig> request = new HttpEntity<>(config, headers); restTemplate.exchange(url, HttpMethod.POST, request, Void.class); } private HttpHeaders createHeaders() { HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", "Bearer " + quayToken); headers.set("Content-Type", "application/json"); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); return headers; } // Data Classes public static class RepositorySecurityReport { private String name; private String namespace; private boolean isPublic; private SecurityScanStatus securityScanStatus; private List<ImageTagSecurity> tags; private VulnerabilitySummary vulnerabilitySummary; // Getters and setters public String getName() { return name; } public void setName(String name) { this.name = name; } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public boolean isPublic() { return isPublic; } public void setPublic(boolean isPublic) { this.isPublic = isPublic; } public SecurityScanStatus getSecurityScanStatus() { return securityScanStatus; } public void setSecurityScanStatus(SecurityScanStatus securityScanStatus) { this.securityScanStatus = securityScanStatus; } public List<ImageTagSecurity> getTags() { return tags; } public void setTags(List<ImageTagSecurity> tags) { this.tags = tags; } public VulnerabilitySummary getVulnerabilitySummary() { return vulnerabilitySummary; } public void setVulnerabilitySummary(VulnerabilitySummary vulnerabilitySummary) { this.vulnerabilitySummary = vulnerabilitySummary; } } public static class ImageSecurityReport { private List<ImageVulnerability> vulnerabilities; private ScanStatus status; private Date scannedDate; // Getters and setters public List<ImageVulnerability> getVulnerabilities() { return vulnerabilities; } public void setVulnerabilities(List<ImageVulnerability> vulnerabilities) { this.vulnerabilities = vulnerabilities; } public ScanStatus getStatus() { return status; } public void setStatus(ScanStatus status) { this.status = status; } public Date getScannedDate() { return scannedDate; } public void setScannedDate(Date scannedDate) { this.scannedDate = scannedDate; } } public static class ImageVulnerability { private String id; private String severity; private String packageName; private String packageVersion; private String fixedInVersion; private String description; private String link; private List<String> references; // Getters and setters public String getId() { return id; } public void setId(String id) { this.id = id; } public String getSeverity() { return severity; } public void setSeverity(String severity) { this.severity = severity; } 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 getFixedInVersion() { return fixedInVersion; } public void setFixedInVersion(String fixedInVersion) { this.fixedInVersion = fixedInVersion; } 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 List<String> getReferences() { return references; } public void setReferences(List<String> references) { this.references = references; } } public static class SecurityScanStatus { private String status; // scanned, scanning, failed, unsupported private Date lastScanned; private String scanResult; // Getters and setters } public static class ImageTagSecurity { private String name; private VulnerabilitySummary vulnerabilitySummary; private Date lastModified; // Getters and setters } public static class VulnerabilitySummary { private int critical; private int high; private int medium; private int low; private int unknown; // Getters and setters public int getCritical() { return critical; } public void setCritical(int critical) { this.critical = critical; } public int getHigh() { return high; } public void setHigh(int high) { this.high = high; } public int getMedium() { return medium; } public void setMedium(int medium) { this.medium = medium; } public int getLow() { return low; } public void setLow(int low) { this.low = low; } public int getUnknown() { return unknown; } public void setUnknown(int unknown) { this.unknown = unknown; } public int getTotal() { return critical + high + medium + low + unknown; } } public static class ScanStatus { private String status; private String message; private Date startedAt; private Date completedAt; // Getters and setters } public static class RobotAccount { private String name; private String token; private String description; private Date created; private List<RobotPermission> permissions; // Getters and setters } public static class RobotPermission { private String repository; private String permission; // Getters and setters } public static class SecurityNotificationConfig { private String event; // vuln_found, repo_updated, build_failed private String method; // webhook, email, slack private Map<String, String> config; private String title; private boolean enabled; // Getters and setters } } Security Scanning Integration
2. Vulnerability Scanner Service
package com.example.quay.security; import org.springframework.stereotype.Service; import java.util.*; import java.util.stream.Collectors; @Service public class VulnerabilityScannerService { private final QuayApiClient quayClient; public VulnerabilityScannerService(QuayApiClient quayClient) { this.quayClient = quayClient; } public SecurityComplianceReport checkImageCompliance(String namespace, String repository, String tag) { List<QuayApiClient.ImageVulnerability> vulnerabilities = quayClient.getImageVulnerabilities(namespace, repository, tag); SecurityComplianceReport report = new SecurityComplianceReport(); report.setNamespace(namespace); report.setRepository(repository); report.setTag(tag); report.setScanDate(new Date()); report.setVulnerabilities(vulnerabilities); // Analyze vulnerabilities analyzeVulnerabilities(vulnerabilities, report); // Check compliance against policies checkCompliancePolicies(report); return report; } private void analyzeVulnerabilities(List<QuayApiClient.ImageVulnerability> vulnerabilities, SecurityComplianceReport report) { Map<String, Long> severityCounts = vulnerabilities.stream() .collect(Collectors.groupingBy( QuayApiClient.ImageVulnerability::getSeverity, Collectors.counting() )); report.setCriticalCount(severityCounts.getOrDefault("Critical", 0L).intValue()); report.setHighCount(severityCounts.getOrDefault("High", 0L).intValue()); report.setMediumCount(severityCounts.getOrDefault("Medium", 0L).intValue()); report.setLowCount(severityCounts.getOrDefault("Low", 0L).intValue()); // Find fixable vulnerabilities List<QuayApiClient.ImageVulnerability> fixable = vulnerabilities.stream() .filter(v -> v.getFixedInVersion() != null && !v.getFixedInVersion().isEmpty()) .collect(Collectors.toList()); report.setFixableVulnerabilities(fixable); // Group by package Map<String, List<QuayApiClient.ImageVulnerability>> byPackage = vulnerabilities.stream() .collect(Collectors.groupingBy(QuayApiClient.ImageVulnerability::getPackageName)); report.setVulnerabilitiesByPackage(byPackage); } private void checkCompliancePolicies(SecurityComplianceReport report) { SecurityPolicy policy = getSecurityPolicy(); boolean compliant = true; List<String> violations = new ArrayList<>(); if (report.getCriticalCount() > policy.getMaxCritical()) { compliant = false; violations.add(String.format("Critical vulnerabilities exceed limit: %d > %d", report.getCriticalCount(), policy.getMaxCritical())); } if (report.getHighCount() > policy.getMaxHigh()) { compliant = false; violations.add(String.format("High vulnerabilities exceed limit: %d > %d", report.getHighCount(), policy.getMaxHigh())); } if (report.getTotalVulnerabilities() > policy.getMaxTotal()) { compliant = false; violations.add(String.format("Total vulnerabilities exceed limit: %d > %d", report.getTotalVulnerabilities(), policy.getMaxTotal())); } report.setCompliant(compliant); report.setPolicyViolations(violations); } public List<String> generateRemediationSteps(SecurityComplianceReport report) { List<String> steps = new ArrayList<>(); if (!report.isCompliant()) { steps.add("Security compliance check failed. Remediation required:"); // Base image upgrade steps.add("1. Consider upgrading base image to a more secure version"); // Package updates Map<String, String> packageUpdates = new HashMap<>(); for (QuayApiClient.ImageVulnerability vuln : report.getFixableVulnerabilities()) { if (vuln.getFixedInVersion() != null) { packageUpdates.put(vuln.getPackageName(), vuln.getFixedInVersion()); } } if (!packageUpdates.isEmpty()) { steps.add("2. Update vulnerable packages:"); packageUpdates.forEach((pkg, version) -> steps.add(" - " + pkg + " → " + version)); } // Security patches steps.add("3. Apply security patches for unfixable vulnerabilities"); steps.add("4. Rescan image after remediation"); } else { steps.add("Image is compliant with security policies"); } return steps; } public ScanTrend analyzeScanTrends(String namespace, String repository, int days) { // Implementation to analyze vulnerability trends over time // This would fetch historical scan data and analyze patterns return new ScanTrend(); } private SecurityPolicy getSecurityPolicy() { // Load from configuration SecurityPolicy policy = new SecurityPolicy(); policy.setMaxCritical(0); policy.setMaxHigh(2); policy.setMaxMedium(10); policy.setMaxTotal(20); return policy; } // Data Classes public static class SecurityComplianceReport { private String namespace; private String repository; private String tag; private Date scanDate; private List<QuayApiClient.ImageVulnerability> vulnerabilities; private int criticalCount; private int highCount; private int mediumCount; private int lowCount; private List<QuayApiClient.ImageVulnerability> fixableVulnerabilities; private Map<String, List<QuayApiClient.ImageVulnerability>> vulnerabilitiesByPackage; private boolean compliant; private List<String> policyViolations; // Getters and setters public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getRepository() { return repository; } public void setRepository(String repository) { this.repository = repository; } public String getTag() { return tag; } public void setTag(String tag) { this.tag = tag; } public Date getScanDate() { return scanDate; } public void setScanDate(Date scanDate) { this.scanDate = scanDate; } public List<QuayApiClient.ImageVulnerability> getVulnerabilities() { return vulnerabilities; } public void setVulnerabilities(List<QuayApiClient.ImageVulnerability> vulnerabilities) { this.vulnerabilities = vulnerabilities; } 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<QuayApiClient.ImageVulnerability> getFixableVulnerabilities() { return fixableVulnerabilities; } public void setFixableVulnerabilities(List<QuayApiClient.ImageVulnerability> fixableVulnerabilities) { this.fixableVulnerabilities = fixableVulnerabilities; } public Map<String, List<QuayApiClient.ImageVulnerability>> getVulnerabilitiesByPackage() { return vulnerabilitiesByPackage; } public void setVulnerabilitiesByPackage(Map<String, List<QuayApiClient.ImageVulnerability>> vulnerabilitiesByPackage) { this.vulnerabilitiesByPackage = vulnerabilitiesByPackage; } public boolean isCompliant() { return compliant; } public void setCompliant(boolean compliant) { this.compliant = compliant; } public List<String> getPolicyViolations() { return policyViolations; } public void setPolicyViolations(List<String> policyViolations) { this.policyViolations = policyViolations; } public int getTotalVulnerabilities() { return criticalCount + highCount + mediumCount + lowCount; } } public static class SecurityPolicy { private int maxCritical; private int maxHigh; private int maxMedium; private int maxLow; private int maxTotal; private List<String> allowedBaseImages; private List<String> bannedPackages; // Getters and setters public int getMaxCritical() { return maxCritical; } public void setMaxCritical(int maxCritical) { this.maxCritical = maxCritical; } public int getMaxHigh() { return maxHigh; } public void setMaxHigh(int maxHigh) { this.maxHigh = maxHigh; } public int getMaxMedium() { return maxMedium; } public void setMaxMedium(int maxMedium) { this.maxMedium = maxMedium; } public int getMaxLow() { return maxLow; } public void setMaxLow(int maxLow) { this.maxLow = maxLow; } public int getMaxTotal() { return maxTotal; } public void setMaxTotal(int maxTotal) { this.maxTotal = maxTotal; } public List<String> getAllowedBaseImages() { return allowedBaseImages; } public void setAllowedBaseImages(List<String> allowedBaseImages) { this.allowedBaseImages = allowedBaseImages; } public List<String> getBannedPackages() { return bannedPackages; } public void setBannedPackages(List<String> bannedPackages) { this.bannedPackages = bannedPackages; } } public static class ScanTrend { private Map<Date, Integer> vulnerabilityTrend; private double trendSlope; private String trendDirection; // improving, worsening, stable // Getters and setters } } Image Signing and Verification
3. Content Trust Service
package com.example.quay.security; import org.springframework.stereotype.Service; import java.security.*; import java.util.Base64; @Service public class ContentTrustService { private final QuayApiClient quayClient; public ContentTrustService(QuayApiClient quayClient) { this.quayClient = quayClient; } public SignatureResult signImage(String namespace, String repository, String tag, String digest, PrivateKey privateKey) { try { String contentToSign = String.format("%s/%s:%s@%s", namespace, repository, tag, digest); Signature signature = Signature.getInstance("SHA256withRSA"); signature.initSign(privateKey); signature.update(contentToSign.getBytes()); byte[] digitalSignature = signature.sign(); String signatureBase64 = Base64.getEncoder().encodeToString(digitalSignature); // Store signature in Quay metadata storeSignatureInQuay(namespace, repository, tag, digest, signatureBase64); return new SignatureResult(true, "Image signed successfully", signatureBase64); } catch (Exception e) { return new SignatureResult(false, "Signing failed: " + e.getMessage(), null); } } public VerificationResult verifyImage(String namespace, String repository, String tag, String digest, PublicKey publicKey) { try { // Retrieve signature from Quay String storedSignature = retrieveSignatureFromQuay(namespace, repository, tag, digest); if (storedSignature == null) { return new VerificationResult(false, "No signature found for image"); } String contentToVerify = String.format("%s/%s:%s@%s", namespace, repository, tag, digest); Signature signature = Signature.getInstance("SHA256withRSA"); signature.initVerify(publicKey); signature.update(contentToVerify.getBytes()); byte[] signatureBytes = Base64.getDecoder().decode(storedSignature); boolean verified = signature.verify(signatureBytes); if (verified) { return new VerificationResult(true, "Image signature verified"); } else { return new VerificationResult(false, "Image signature verification failed"); } } catch (Exception e) { return new VerificationResult(false, "Verification failed: " + e.getMessage()); } } public KeyPair generateSigningKeyPair() throws NoSuchAlgorithmException { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(2048); return keyGen.generateKeyPair(); } private void storeSignatureInQuay(String namespace, String repository, String tag, String digest, String signature) { // Implementation to store signature in Quay metadata // This would use Quay's API to store the signature as image metadata } private String retrieveSignatureFromQuay(String namespace, String repository, String tag, String digest) { // Implementation to retrieve signature from Quay metadata // This would use Quay's API to retrieve the stored signature return "stored-signature-base64"; // Placeholder } // Data Classes public static class SignatureResult { private final boolean success; private final String message; private final String signature; public SignatureResult(boolean success, String message, String signature) { this.success = success; this.message = message; this.signature = signature; } // Getters public boolean isSuccess() { return success; } public String getMessage() { return message; } public String getSignature() { return signature; } } public static class VerificationResult { private final boolean verified; private final String message; public VerificationResult(boolean verified, String message) { this.verified = verified; this.message = message; } // Getters public boolean isVerified() { return verified; } public String getMessage() { return message; } } } Access Control and RBAC
4. Access Management Service
package com.example.quay.security; import org.springframework.stereotype.Service; import java.util.*; @Service public class AccessManagementService { private final QuayApiClient quayClient; public AccessManagementService(QuayApiClient quayClient) { this.quayClient = quayClient; } public RepositoryAccessReport analyzeRepositoryAccess(String namespace, String repository) { RepositoryAccessReport report = new RepositoryAccessReport(); report.setNamespace(namespace); report.setRepository(repository); report.setAnalysisDate(new Date()); // Analyze user permissions analyzeUserPermissions(namespace, repository, report); // Analyze robot account permissions analyzeRobotPermissions(namespace, repository, report); // Check for security risks checkSecurityRisks(report); return report; } private void analyzeUserPermissions(String namespace, String repository, RepositoryAccessReport report) { // Implementation to analyze user permissions // This would fetch user permissions and identify potential risks List<PermissionFinding> findings = new ArrayList<>(); // Example checks if (hasTooManyAdmins(report)) { findings.add(new PermissionFinding( "TOO_MANY_ADMINS", "High", "Repository has more than 2 users with admin permissions" )); } if (hasExternalUsersWithWriteAccess(report)) { findings.add(new PermissionFinding( "EXTERNAL_WRITE_ACCESS", "Medium", "External users have write access to repository" )); } report.setPermissionFindings(findings); } private void analyzeRobotPermissions(String namespace, String repository, RepositoryAccessReport report) { // Analyze robot account permissions and usage List<RobotFinding> robotFindings = new ArrayList<>(); // Check for overly permissive robot accounts if (hasOverlyPermissiveRobots(report)) { robotFindings.add(new RobotFinding( "OVERLY_PERMISSIVE_ROBOT", "High", "Robot account has admin permissions without justification" )); } // Check for unused robot accounts if (hasUnusedRobots(report)) { robotFindings.add(new RobotFinding( "UNUSED_ROBOT", "Low", "Robot account hasn't been used in 90 days" )); } report.setRobotFindings(robotFindings); } private void checkSecurityRisks(RepositoryAccessReport report) { List<SecurityRisk> risks = new ArrayList<>(); // Public repository with sensitive data if (isPublicWithSensitiveData(report)) { risks.add(new SecurityRisk( "PUBLIC_SENSITIVE_DATA", "Critical", "Public repository may contain sensitive data" )); } // No 2FA enforcement if (!has2FAEnforcement(report)) { risks.add(new SecurityRisk( "NO_2FA_ENFORCEMENT", "Medium", "Two-factor authentication not enforced for organization" )); } report.setSecurityRisks(risks); } public AccessComplianceReport checkAccessCompliance(String namespace) { AccessComplianceReport report = new AccessComplianceReport(); report.setNamespace(namespace); report.setCheckDate(new Date()); // Check organization-wide compliance checkOrganizationCompliance(namespace, report); // Check repository-specific compliance checkRepositoryCompliance(namespace, report); return report; } private void checkOrganizationCompliance(String namespace, AccessComplianceReport report) { List<ComplianceFinding> findings = new ArrayList<>(); // Check if 2FA is enabled if (!is2FAEnabled(namespace)) { findings.add(new ComplianceFinding( "2FA_NOT_ENABLED", "Organization does not enforce two-factor authentication", "HIGH" )); } // Check team structure if (!hasProperTeamStructure(namespace)) { findings.add(new ComplianceFinding( "IMPROPER_TEAM_STRUCTURE", "Organization lacks proper team hierarchy and access control", "MEDIUM" )); } report.setOrganizationFindings(findings); } private void checkRepositoryCompliance(String namespace, AccessComplianceReport report) { List<RepositoryCompliance> repoCompliance = new ArrayList<>(); // Check each repository in the namespace // This would iterate through all repositories and check their compliance RepositoryCompliance comp = new RepositoryCompliance(); comp.setRepositoryName("example-repo"); comp.setCompliant(true); comp.setFindings(Collections.emptyList()); repoCompliance.add(comp); report.setRepositoryCompliance(repoCompliance); } // Helper methods (simplified implementations) private boolean hasTooManyAdmins(RepositoryAccessReport report) { return false; } private boolean hasExternalUsersWithWriteAccess(RepositoryAccessReport report) { return false; } private boolean hasOverlyPermissiveRobots(RepositoryAccessReport report) { return false; } private boolean hasUnusedRobots(RepositoryAccessReport report) { return false; } private boolean isPublicWithSensitiveData(RepositoryAccessReport report) { return false; } private boolean has2FAEnforcement(RepositoryAccessReport report) { return false; } private boolean is2FAEnabled(String namespace) { return true; } private boolean hasProperTeamStructure(String namespace) { return true; } // Data Classes public static class RepositoryAccessReport { private String namespace; private String repository; private Date analysisDate; private List<PermissionFinding> permissionFindings; private List<RobotFinding> robotFindings; private List<SecurityRisk> securityRisks; // Getters and setters public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getRepository() { return repository; } public void setRepository(String repository) { this.repository = repository; } public Date getAnalysisDate() { return analysisDate; } public void setAnalysisDate(Date analysisDate) { this.analysisDate = analysisDate; } public List<PermissionFinding> getPermissionFindings() { return permissionFindings; } public void setPermissionFindings(List<PermissionFinding> permissionFindings) { this.permissionFindings = permissionFindings; } public List<RobotFinding> getRobotFindings() { return robotFindings; } public void setRobotFindings(List<RobotFinding> robotFindings) { this.robotFindings = robotFindings; } public List<SecurityRisk> getSecurityRisks() { return securityRisks; } public void setSecurityRisks(List<SecurityRisk> securityRisks) { this.securityRisks = securityRisks; } } public static class PermissionFinding { private String code; private String severity; private String description; public PermissionFinding(String code, String severity, String description) { this.code = code; this.severity = severity; this.description = description; } // Getters public String getCode() { return code; } public String getSeverity() { return severity; } public String getDescription() { return description; } } public static class RobotFinding { private String code; private String severity; private String description; public RobotFinding(String code, String severity, String description) { this.code = code; this.severity = severity; this.description = description; } // Getters public String getCode() { return code; } public String getSeverity() { return severity; } public String getDescription() { return description; } } public static class SecurityRisk { private String code; private String severity; private String description; public SecurityRisk(String code, String severity, String description) { this.code = code; this.severity = severity; this.description = description; } // Getters public String getCode() { return code; } public String getSeverity() { return severity; } public String getDescription() { return description; } } public static class AccessComplianceReport { private String namespace; private Date checkDate; private List<ComplianceFinding> organizationFindings; private List<RepositoryCompliance> repositoryCompliance; // Getters and setters public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public Date getCheckDate() { return checkDate; } public void setCheckDate(Date checkDate) { this.checkDate = checkDate; } public List<ComplianceFinding> getOrganizationFindings() { return organizationFindings; } public void setOrganizationFindings(List<ComplianceFinding> organizationFindings) { this.organizationFindings = organizationFindings; } public List<RepositoryCompliance> getRepositoryCompliance() { return repositoryCompliance; } public void setRepositoryCompliance(List<RepositoryCompliance> repositoryCompliance) { this.repositoryCompliance = repositoryCompliance; } } public static class ComplianceFinding { private String code; private String description; private String severity; public ComplianceFinding(String code, String description, String severity) { this.code = code; this.description = description; this.severity = severity; } // Getters public String getCode() { return code; } public String getDescription() { return description; } public String getSeverity() { return severity; } } public static class RepositoryCompliance { private String repositoryName; private boolean compliant; private List<ComplianceFinding> findings; // Getters and setters public String getRepositoryName() { return repositoryName; } public void setRepositoryName(String repositoryName) { this.repositoryName = repositoryName; } public boolean isCompliant() { return compliant; } public void setCompliant(boolean compliant) { this.compliant = compliant; } public List<ComplianceFinding> getFindings() { return findings; } public void setFindings(List<ComplianceFinding> findings) { this.findings = findings; } } } CI/CD Integration
5. Security Gate Service
package com.example.quay.security; import org.springframework.stereotype.Service; import java.util.*; @Service public class SecurityGateService { private final VulnerabilityScannerService vulnerabilityScanner; private final ContentTrustService contentTrustService; private final AccessManagementService accessManagementService; public SecurityGateService(VulnerabilityScannerService vulnerabilityScanner, ContentTrustService contentTrustService, AccessManagementService accessManagementService) { this.vulnerabilityScanner = vulnerabilityScanner; this.contentTrustService = contentTrustService; this.accessManagementService = accessManagementService; } public DeploymentApproval checkDeploymentApproval(String namespace, String repository, String tag, String digest) { DeploymentApproval approval = new DeploymentApproval(); approval.setNamespace(namespace); approval.setRepository(repository); approval.setTag(tag); approval.setDigest(digest); approval.setCheckTime(new Date()); // Check vulnerability compliance VulnerabilityScannerService.SecurityComplianceReport vulnReport = vulnerabilityScanner.checkImageCompliance(namespace, repository, tag); approval.setVulnerabilityReport(vulnReport); // Check image signature ContentTrustService.VerificationResult signatureResult = contentTrustService.verifyImage(namespace, repository, tag, digest, getPublicKey()); approval.setSignatureVerified(signatureResult.isVerified()); approval.setSignatureVerificationMessage(signatureResult.getMessage()); // Check access compliance AccessManagementService.RepositoryAccessReport accessReport = accessManagementService.analyzeRepositoryAccess(namespace, repository); approval.setAccessReport(accessReport); // Make approval decision makeApprovalDecision(approval); return approval; } private void makeApprovalDecision(DeploymentApproval approval) { boolean approved = true; List<String> rejectionReasons = new ArrayList<>(); // Check vulnerability compliance if (!approval.getVulnerabilityReport().isCompliant()) { approved = false; rejectionReasons.add("Image has critical/high vulnerabilities"); } // Check signature verification if (!approval.isSignatureVerified()) { approved = false; rejectionReasons.add("Image signature verification failed"); } // Check access security if (hasCriticalSecurityRisks(approval.getAccessReport())) { approved = false; rejectionReasons.add("Repository has critical security risks"); } approval.setApproved(approved); approval.setRejectionReasons(rejectionReasons); if (approved) { approval.setApprovalMessage("Image meets all security requirements for deployment"); } else { approval.setApprovalMessage("Image rejected due to security policy violations"); } } public SecurityGateReport generateSecurityReport(String namespace, String repository) { SecurityGateReport report = new SecurityGateReport(); report.setNamespace(namespace); report.setRepository(repository); report.setGenerationDate(new Date()); // Generate comprehensive security report generateVulnerabilitySummary(namespace, repository, report); generateAccessSummary(namespace, repository, report); generateComplianceSummary(namespace, repository, report); return report; } private void generateVulnerabilitySummary(String namespace, String repository, SecurityGateReport report) { // Implementation to generate vulnerability summary // This would analyze all image tags and their vulnerability status } private void generateAccessSummary(String namespace, String repository, SecurityGateReport report) { // Implementation to generate access control summary // This would analyze user and robot account permissions } private void generateComplianceSummary(String namespace, String repository, SecurityGateReport report) { // Implementation to generate compliance summary // This would check against organizational security policies } private PublicKey getPublicKey() { // Implementation to retrieve the public key for signature verification // This could be from a key store, configuration, or environment variable return null; // Placeholder } private boolean hasCriticalSecurityRisks(AccessManagementService.RepositoryAccessReport report) { return report.getSecurityRisks().stream() .anyMatch(risk -> "Critical".equals(risk.getSeverity())); } // Data Classes public static class DeploymentApproval { private String namespace; private String repository; private String tag; private String digest; private Date checkTime; private VulnerabilityScannerService.SecurityComplianceReport vulnerabilityReport; private boolean signatureVerified; private String signatureVerificationMessage; private AccessManagementService.RepositoryAccessReport accessReport; private boolean approved; private List<String> rejectionReasons; private String approvalMessage; // Getters and setters public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getRepository() { return repository; } public void setRepository(String repository) { this.repository = repository; } public String getTag() { return tag; } public void setTag(String tag) { this.tag = tag; } public String getDigest() { return digest; } public void setDigest(String digest) { this.digest = digest; } public Date getCheckTime() { return checkTime; } public void setCheckTime(Date checkTime) { this.checkTime = checkTime; } public VulnerabilityScannerService.SecurityComplianceReport getVulnerabilityReport() { return vulnerabilityReport; } public void setVulnerabilityReport(VulnerabilityScannerService.SecurityComplianceReport vulnerabilityReport) { this.vulnerabilityReport = vulnerabilityReport; } public boolean isSignatureVerified() { return signatureVerified; } public void setSignatureVerified(boolean signatureVerified) { this.signatureVerified = signatureVerified; } public String getSignatureVerificationMessage() { return signatureVerificationMessage; } public void setSignatureVerificationMessage(String signatureVerificationMessage) { this.signatureVerificationMessage = signatureVerificationMessage; } public AccessManagementService.RepositoryAccessReport getAccessReport() { return accessReport; } public void setAccessReport(AccessManagementService.RepositoryAccessReport accessReport) { this.accessReport = accessReport; } public boolean isApproved() { return approved; } public void setApproved(boolean approved) { this.approved = approved; } public List<String> getRejectionReasons() { return rejectionReasons; } public void setRejectionReasons(List<String> rejectionReasons) { this.rejectionReasons = rejectionReasons; } public String getApprovalMessage() { return approvalMessage; } public void setApprovalMessage(String approvalMessage) { this.approvalMessage = approvalMessage; } } public static class SecurityGateReport { private String namespace; private String repository; private Date generationDate; private VulnerabilitySummary vulnerabilitySummary; private AccessSummary accessSummary; private ComplianceSummary complianceSummary; private List<SecurityRecommendation> recommendations; // Getters and setters public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getRepository() { return repository; } public void setRepository(String repository) { this.repository = repository; } public Date getGenerationDate() { return generationDate; } public void setGenerationDate(Date generationDate) { this.generationDate = generationDate; } public VulnerabilitySummary getVulnerabilitySummary() { return vulnerabilitySummary; } public void setVulnerabilitySummary(VulnerabilitySummary vulnerabilitySummary) { this.vulnerabilitySummary = vulnerabilitySummary; } public AccessSummary getAccessSummary() { return accessSummary; } public void setAccessSummary(AccessSummary accessSummary) { this.accessSummary = accessSummary; } public ComplianceSummary getComplianceSummary() { return complianceSummary; } public void setComplianceSummary(ComplianceSummary complianceSummary) { this.complianceSummary = complianceSummary; } public List<SecurityRecommendation> getRecommendations() { return recommendations; } public void setRecommendations(List<SecurityRecommendation> recommendations) { this.recommendations = recommendations; } } public static class VulnerabilitySummary { private int totalImages; private int compliantImages; private int nonCompliantImages; private Map<String, Integer> severityDistribution; private List<String> mostVulnerableImages; // Getters and setters } public static class AccessSummary { private int totalUsers; private int totalRobots; private int adminUsers; private int writeUsers; private int readUsers; private List<String> securityFindings; // Getters and setters } public static class ComplianceSummary { private boolean organizationCompliant; private int compliantRepositories; private int nonCompliantRepositories; private List<String> complianceIssues; // Getters and setters } public static class SecurityRecommendation { private String category; private String description; private String priority; // HIGH, MEDIUM, LOW private String action; // Getters and setters } } Configuration
6. Spring Boot Configuration
package com.example.quay.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class QuaySecurityConfig { @Value("${quay.security.token}") private String quayToken; @Value("${quay.security.base-url:https://quay.io}") private String quayBaseUrl; @Value("${quay.security.policy.max-critical:0}") private int maxCriticalVulnerabilities; @Value("${quay.security.policy.max-high:2}") private int maxHighVulnerabilities; @Value("${quay.security.policy.max-total:20}") private int maxTotalVulnerabilities; @Bean public QuayApiClient quayApiClient() { return new QuayApiClient(quayToken, quayBaseUrl); } @Bean public VulnerabilityScannerService.SecurityPolicy securityPolicy() { VulnerabilityScannerService.SecurityPolicy policy = new VulnerabilityScannerService.SecurityPolicy(); policy.setMaxCritical(maxCriticalVulnerabilities); policy.setMaxHigh(maxHighVulnerabilities); policy.setMaxTotal(maxTotalVulnerabilities); return policy; } } // application.yml quay: security: token: ${QUAY_SECURITY_TOKEN} base-url: https://quay.io policy: max-critical: 0 max-high: 2 max-medium: 10 max-total: 20 signing: key-algorithm: RSA key-size: 2048 security: gate: require-signature: true block-critical-vulns: true enforce-access-control: true logging: level: com.example.quay.security: DEBUG This comprehensive Quay.io security integration provides:
- Vulnerability Management - Scanning, analysis, and compliance checking
- Content Trust - Image signing and verification
- Access Control - RBAC analysis and security auditing
- Security Gates - Automated deployment approvals
- Compliance Reporting - Comprehensive security reporting
The system can be integrated into your CI/CD pipeline to enforce security policies and ensure container images meet organizational security standards before deployment.