Introduction
Conftest is a utility that helps you write tests against structured configuration data using Open Policy Agent (OPA) and Rego policies. For Java applications, Conftest can validate Kubernetes manifests, configuration files, CI/CD pipelines, and infrastructure-as-code templates.
Architecture Overview
1. Conftest Integration Pipeline
Java Application Configs → Conftest → Policy Validation → Results ↓ ↓ ↓ Kubernetes Manifests Rego Policies Pass/Fail ↓ ↓ ↓ Dockerfiles Custom Rules Enforcement ↓ ↓ ↓ Helm Charts Best Practices CI/CD Gates
Setup and Dependencies
1. Maven Dependencies
<properties> <opa.version>0.62.1</opa.version> <jackson.version>2.15.2</jackson.version> </properties> <dependencies> <!-- Open Policy Agent Java SDK --> <dependency> <groupId>com.openpolicyagent</groupId> <artifactId>opa-client</artifactId> <version>${opa.version}</version> </dependency> <!-- Jackson for JSON processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <!-- YAML processing --> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-yaml</artifactId> <version>${jackson.version}</version> </dependency> <!-- JUnit 5 for testing --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.0</version> <scope>test</scope> </dependency> <!-- Test Containers for OPA testing --> <dependency> <groupId>org.testcontainers</groupId> <artifactId>junit-jupiter</artifactId> <version>1.19.3</version> <scope>test</scope> </dependency> </dependencies> 2. Conftest Installation and Setup
# Install Conftest curl -L -o conftest https://github.com/open-policy-agent/conftest/releases/download/v0.45.0/conftest_0.45.0_Linux_x86_64.tar.gz tar xzf conftest_0.45.0_Linux_x86_64.tar.gz sudo mv conftest /usr/local/bin # Create policy directory structure mkdir -p policies/kubernetes mkdir -p policies/docker mkdir -p policies/helm mkdir -p policies/ci
Rego Policy Development
1. Kubernetes Security Policies
# policies/kubernetes/security.rego package kubernetes.security import future.keywords.in # Deny containers running as root deny[msg] { input.kind == "Deployment" spec := input.spec.template.spec container := spec.containers[_] securityContext := container.securityContext securityContext.runAsUser == 0 msg := sprintf("Container '%s' must not run as root", [container.name]) } # Require non-root user in SecurityContext deny[msg] { input.kind == "Deployment" spec := input.spec.template.spec not spec.securityContext.runAsNonRoot msg := "Deployment must set runAsNonRoot to true" } # Require read-only root filesystem deny[msg] { input.kind == "Deployment" container := input.spec.template.spec.containers[_] not container.securityContext.readOnlyRootFilesystem msg := sprintf("Container '%s' must use read-only root filesystem", [container.name]) } # Require resource limits deny[msg] { input.kind == "Deployment" container := input.spec.template.spec.containers[_] not container.resources.limits msg := sprintf("Container '%s' must have resource limits", [container.name]) } # CPU limits validation warn[msg] { input.kind == "Deployment" container := input.spec.template.spec.containers[_] limits := container.resources.limits limits.cpu to_number(limits.cpu) > 2 msg := sprintf("Container '%s' has high CPU limit: %s", [container.name, limits.cpu]) } # Memory limits validation warn[msg] { input.kind == "Deployment" container := input.spec.template.spec.containers[_] limits := container.resources.limits limits.memory regex.match("^[0-9]+Gi$", limits.memory) to_number(trim(limits.memory, "Gi")) > 4 msg := sprintf("Container '%s' has high memory limit: %s", [container.name, limits.memory]) } 2. Java Application Specific Policies
# policies/kubernetes/java.rego package kubernetes.java # Require JVM memory settings for Java apps deny[msg] { input.kind == "Deployment" container := input.spec.template.spec.containers[_] is_java_container(container) not has_jvm_memory_settings(container) msg := sprintf("Java container '%s' must have JVM memory settings", [container.name]) } # Validate JVM memory matches container limits warn[msg] { input.kind == "Deployment" container := input.spec.template.spec.containers[_] is_java_container(container) jvm_memory := get_jvm_memory(container) container_memory := get_container_memory(container) jvm_memory > container_memory * 0.8 msg := sprintf("Java container '%s' JVM memory (%s) is too close to container limit (%s)", [container.name, jvm_memory, container_memory]) } # Require liveness and readiness probes for Java apps deny[msg] { input.kind == "Deployment" container := input.spec.template.spec.containers[_] is_java_container(container) not container.livenessProbe msg := sprintf("Java container '%s' must have liveness probe", [container.name]) } deny[msg] { input.kind == "Deployment" container := input.spec.template.spec.containers[_] is_java_container(container) not container.readinessProbe msg := sprintf("Java container '%s' must have readiness probe", [container.name]) } # Helper functions is_java_container(container) { container.image =~ ".*java.*" } else { container.image =~ ".*jvm.*" } else { container.env[_].name == "JAVA_OPTS" } has_jvm_memory_settings(container) { container.env[_].name == "JAVA_OPTS" container.env[_].value =~ ".*Xmx.*" } get_jvm_memory(container) = memory { env := container.env[_] env.name == "JAVA_OPTS" matches := regex.find_all_string_submatch_n("-Xmx([0-9]+[MG])", env.value, -1) memory_str := matches[0][1] memory = convert_memory_to_mb(memory_str) } get_container_memory(container) = memory { limits := container.resources.limits memory_str := limits.memory memory = convert_memory_to_mb(memory_str) } convert_memory_to_mb(str) = result { regex.match("^[0-9]+Mi$", str) result = to_number(trim(str, "Mi")) } else = result { regex.match("^[0-9]+Gi$", str) result = to_number(trim(str, "Gi")) * 1024 } 3. Dockerfile Policies
# policies/docker/java.rego package docker.java # Require specific base images for Java deny[msg] { input[i].Cmd == "from" base_image := input[i+1].Value not is_allowed_java_base_image(base_image) msg := sprintf("Base image '%s' is not allowed for Java applications. Use eclipse-temurin or openjdk official images", [base_image]) } # Require non-root user deny[msg] { not has_user_instruction(input) msg := "Dockerfile must create and use non-root user" } # Prevent latest tag warn[msg] { input[i].Cmd == "from" base_image := input[i+1].Value base_image =~ ".*:latest" msg := "Avoid using 'latest' tag for base images" } # Require .dockerignore deny[msg] { not has_dockerignore msg := "Project must include .dockerignore file" } # Helper functions is_allowed_java_base_image(image) { image =~ "eclipse-temurin:*" } else { image =~ "openjdk:*" } else { image =~ "amazoncorretto:*" } has_user_instruction(dockerfile) { line := dockerfile[_] line.Cmd == "user" line.Value != "root" } Java Integration with Conftest
1. Conftest Service Implementation
@Service @Slf4j public class ConftestService { private final ObjectMapper objectMapper; private final ObjectMapper yamlMapper; private final ProcessBuilder processBuilder; public ConftestService() { this.objectMapper = new ObjectMapper(); this.yamlMapper = new ObjectMapper(new YAMLFactory()); this.processBuilder = new ProcessBuilder(); } public PolicyValidationResult validateKubernetesManifest(String manifestPath, String policyPath) { try { List<String> command = Arrays.asList( "conftest", "test", manifestPath, "--policy", policyPath, "--output", "json", "--all-namespaces" ); Process process = processBuilder.command(command).start(); String output = readProcessOutput(process); int exitCode = process.waitFor(); return parseValidationResult(output, exitCode); } catch (Exception e) { log.error("Failed to validate Kubernetes manifest: {}", manifestPath, e); return PolicyValidationResult.error(e.getMessage()); } } public PolicyValidationResult validateDockerfile(String dockerfilePath, String policyPath) { try { List<String> command = Arrays.asList( "conftest", "test", dockerfilePath, "--policy", policyPath, "--parser", "dockerfile", "--output", "json" ); Process process = processBuilder.command(command).start(); String output = readProcessOutput(process); int exitCode = process.waitFor(); return parseValidationResult(output, exitCode); } catch (Exception e) { log.error("Failed to validate Dockerfile: {}", dockerfilePath, e); return PolicyValidationResult.error(e.getMessage()); } } public PolicyValidationResult validateJsonConfiguration(String configPath, String policyPath) { try { List<String> command = Arrays.asList( "conftest", "test", configPath, "--policy", policyPath, "--output", "json" ); Process process = processBuilder.command(command).start(); String output = readProcessOutput(process); int exitCode = process.waitFor(); return parseValidationResult(output, exitCode); } catch (Exception e) { log.error("Failed to validate JSON configuration: {}", configPath, e); return PolicyValidationResult.error(e.getMessage()); } } public PolicyValidationResult validateYamlConfiguration(String configPath, String policyPath) { try { List<String> command = Arrays.asList( "conftest", "test", configPath, "--policy", policyPath, "--output", "json" ); Process process = processBuilder.command(command).start(); String output = readProcessOutput(process); int exitCode = process.waitFor(); return parseValidationResult(output, exitCode); } catch (Exception e) { log.error("Failed to validate YAML configuration: {}", configPath, e); return PolicyValidationResult.error(e.getMessage()); } } public BatchValidationResult validateMultipleFiles(List<String> filePaths, String policyPath) { BatchValidationResult batchResult = new BatchValidationResult(); for (String filePath : filePaths) { PolicyValidationResult result = validateFileBasedOnType(filePath, policyPath); batchResult.addResult(filePath, result); } return batchResult; } private PolicyValidationResult validateFileBasedOnType(String filePath, String policyPath) { if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) { return validateYamlConfiguration(filePath, policyPath); } else if (filePath.endsWith(".json")) { return validateJsonConfiguration(filePath, policyPath); } else if (filePath.equals("Dockerfile")) { return validateDockerfile(filePath, policyPath); } else { return validateKubernetesManifest(filePath, policyPath); } } private String readProcessOutput(Process process) throws IOException { try (BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream()))) { return reader.lines().collect(Collectors.joining("\n")); } } private PolicyValidationResult parseValidationResult(String output, int exitCode) { try { if (output.trim().isEmpty()) { return PolicyValidationResult.success(); } List<PolicyResult> results = objectMapper.readValue(output, objectMapper.getTypeFactory().constructCollectionType(List.class, PolicyResult.class)); return PolicyValidationResult.fromConftestOutput(results, exitCode); } catch (Exception e) { log.error("Failed to parse conftest output: {}", output, e); return PolicyValidationResult.error("Failed to parse validation results: " + e.getMessage()); } } } 2. Policy Validation Models
@Data @Builder @NoArgsConstructor @AllArgsConstructor public class PolicyValidationResult { private boolean success; private List<PolicyViolation> violations; private List<PolicyWarning> warnings; private String error; private Instant timestamp; public static PolicyValidationResult success() { return PolicyValidationResult.builder() .success(true) .violations(Collections.emptyList()) .warnings(Collections.emptyList()) .timestamp(Instant.now()) .build(); } public static PolicyValidationResult error(String error) { return PolicyValidationResult.builder() .success(false) .violations(Collections.emptyList()) .warnings(Collections.emptyList()) .error(error) .timestamp(Instant.now()) .build(); } public static PolicyValidationResult fromConftestOutput(List<PolicyResult> results, int exitCode) { List<PolicyViolation> violations = new ArrayList<>(); List<PolicyWarning> warnings = new ArrayList<>(); for (PolicyResult result : results) { for (PolicyFinding finding : result.getFindings()) { if "deny".equals(finding.getType())) { violations.add(PolicyViolation.fromFinding(finding, result.getFilename())); } else if ("warn".equals(finding.getType())) { warnings.add(PolicyWarning.fromFinding(finding, result.getFilename())); } } } return PolicyValidationResult.builder() .success(exitCode == 0 && violations.isEmpty()) .violations(violations) .warnings(warnings) .timestamp(Instant.now()) .build(); } } @Data @Builder @NoArgsConstructor @AllArgsConstructor public class PolicyViolation { private String filename; private String message; private String policy; private String severity; private Map<String, Object> metadata; public static PolicyViolation fromFinding(PolicyFinding finding, String filename) { return PolicyViolation.builder() .filename(filename) .message(finding.getMessage()) .policy(finding.getPolicy()) .severity("HIGH") .metadata(finding.getMetadata()) .build(); } } @Data @Builder @NoArgsConstructor @AllArgsConstructor public class PolicyWarning { private String filename; private String message; private String policy; private String severity; private Map<String, Object> metadata; public static PolicyWarning fromFinding(PolicyFinding finding, String filename) { return PolicyWarning.builder() .filename(filename) .message(finding.getMessage()) .policy(finding.getPolicy()) .severity("MEDIUM") .metadata(finding.getMetadata()) .build(); } } @Data @NoArgsConstructor @AllArgsConstructor public class PolicyResult { private String filename; private List<PolicyFinding> findings; @Data @NoArgsConstructor @AllArgsConstructor public static class PolicyFinding { private String type; private String message; private String policy; private Map<String, Object> metadata; } } @Data @Builder @NoArgsConstructor @AllArgsConstructor public class BatchValidationResult { private Map<String, PolicyValidationResult> results; private boolean overallSuccess; private int totalFiles; private int passedFiles; private int failedFiles; public BatchValidationResult() { this.results = new HashMap<>(); } public void addResult(String filename, PolicyValidationResult result) { results.put(filename, result); recalculateMetrics(); } private void recalculateMetrics() { totalFiles = results.size(); passedFiles = (int) results.values().stream() .filter(PolicyValidationResult::isSuccess) .count(); failedFiles = totalFiles - passedFiles; overallSuccess = failedFiles == 0; } } Spring Boot Integration
1. Configuration Properties
@Configuration @ConfigurationProperties(prefix = "conftest") @Data public class ConftestProperties { private String policyDirectory = "policies"; private Map<String, String> policyMappings = new HashMap<>(); private Validation validation = new Validation(); @Data public static class Validation { private boolean failOnWarning = false; private boolean enabled = true; private List<String> excludedFiles = List.of(); private int timeoutSeconds = 30; } public String getPolicyPath(String fileType) { return policyMappings.getOrDefault(fileType, policyDirectory + "/" + fileType); } } 2. REST API for Policy Validation
@RestController @RequestMapping("/api/policies") @Slf4j public class PolicyValidationController { private final ConftestService conftestService; private final ConftestProperties properties; public PolicyValidationController(ConftestService conftestService, ConftestProperties properties) { this.conftestService = conftestService; this.properties = properties; } @PostMapping("/validate/kubernetes") public ResponseEntity<PolicyValidationResult> validateKubernetesManifest( @RequestBody KubernetesManifestValidationRequest request) { try { PolicyValidationResult result = conftestService.validateKubernetesManifest( request.getManifestPath(), properties.getPolicyPath("kubernetes") ); return ResponseEntity.ok(result); } catch (Exception e) { log.error("Kubernetes manifest validation failed", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(PolicyValidationResult.error(e.getMessage())); } } @PostMapping("/validate/dockerfile") public ResponseEntity<PolicyValidationResult> validateDockerfile( @RequestBody DockerfileValidationRequest request) { try { PolicyValidationResult result = conftestService.validateDockerfile( request.getDockerfilePath(), properties.getPolicyPath("docker") ); return ResponseEntity.ok(result); } catch (Exception e) { log.error("Dockerfile validation failed", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(PolicyValidationResult.error(e.getMessage())); } } @PostMapping("/validate/batch") public ResponseEntity<BatchValidationResult> validateBatch( @RequestBody BatchValidationRequest request) { try { BatchValidationResult result = conftestService.validateMultipleFiles( request.getFilePaths(), properties.getPolicyPath(request.getPolicyType()) ); return ResponseEntity.ok(result); } catch (Exception e) { log.error("Batch validation failed", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @PostMapping("/validate/custom") public ResponseEntity<PolicyValidationResult> validateWithCustomPolicy( @RequestBody CustomPolicyValidationRequest request) { try { PolicyValidationResult result = conftestService.validateKubernetesManifest( request.getFilePath(), request.getPolicyPath() ); return ResponseEntity.ok(result); } catch (Exception e) { log.error("Custom policy validation failed", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(PolicyValidationResult.error(e.getMessage())); } } } @Data @NoArgsConstructor @AllArgsConstructor public class KubernetesManifestValidationRequest { private String manifestPath; private String namespace; } @Data @NoArgsConstructor @AllArgsConstructor public class DockerfileValidationRequest { private String dockerfilePath; } @Data @NoArgsConstructor @AllArgsConstructor public class BatchValidationRequest { private List<String> filePaths; private String policyType; } @Data @NoArgsConstructor @AllArgsConstructor public class CustomPolicyValidationRequest { private String filePath; private String policyPath; } CI/CD Integration
1. GitHub Actions Workflow
name: Conftest Policy Validation on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: policy-validation: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Java uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' cache: 'maven' - name: Install Conftest run: | curl -L -o conftest.tar.gz https://github.com/open-policy-agent/conftest/releases/download/v0.45.0/conftest_0.45.0_Linux_x86_64.tar.gz tar xzf conftest.tar.gz sudo mv conftest /usr/local/bin/ - name: Build Application run: mvn clean compile -DskipTests - name: Run Policy Validation run: | mvn exec:java -Dexec.mainClass="com.example.policy.validator.PolicyValidator" \ -Dexec.args="--directory k8s --policy policies/kubernetes" - name: Upload Validation Results uses: actions/upload-artifact@v4 if: always() with: name: policy-validation-results path: validation-results/ - name: Fail on Policy Violations if: failure() run: | echo "Policy validation failed. Please check the violations above." exit 1
2. Maven Plugin Integration
@Mojo(name = "validate-policies", defaultPhase = LifecyclePhase.VALIDATE) @Slf4j public class PolicyValidationMojo extends AbstractMojo { @Parameter(property = "policy.directory", defaultValue = "policies") private String policyDirectory; @Parameter(property = "config.directory", defaultValue = "k8s") private String configDirectory; @Parameter(property = "fail.on.warning", defaultValue = "false") private boolean failOnWarning; @Override public void execute() throws MojoExecutionException, MojoFailureException { getLog().info("Starting policy validation..."); try { ConftestService conftestService = new ConftestService(); List<String> configFiles = findConfigFiles(); BatchValidationResult result = conftestService.validateMultipleFiles( configFiles, policyDirectory); printValidationResults(result); if (!result.isOverallSuccess()) { throw new MojoFailureException("Policy validation failed"); } if (failOnWarning && hasWarnings(result)) { throw new MojoFailureException("Policy validation has warnings"); } } catch (Exception e) { getLog().error("Policy validation failed", e); throw new MojoExecutionException("Policy validation failed", e); } } private List<String> findConfigFiles() throws IOException { return Files.walk(Paths.get(configDirectory)) .filter(Files::isRegularFile) .filter(path -> path.toString().endsWith(".yaml") || path.toString().endsWith(".yml")) .map(Path::toString) .collect(Collectors.toList()); } private void printValidationResults(BatchValidationResult result) { getLog().info("Policy Validation Results:"); getLog().info("Total Files: " + result.getTotalFiles()); getLog().info("Passed: " + result.getPassedFiles()); getLog().info("Failed: " + result.getFailedFiles()); for (Map.Entry<String, PolicyValidationResult> entry : result.getResults().entrySet()) { PolicyValidationResult fileResult = entry.getValue(); if (!fileResult.isSuccess()) { getLog().warn("File: " + entry.getKey()); for (PolicyViolation violation : fileResult.getViolations()) { getLog().warn(" VIOLATION: " + violation.getMessage()); } for (PolicyWarning warning : fileResult.getWarnings()) { getLog().warn(" WARNING: " + warning.getMessage()); } } } } private boolean hasWarnings(BatchValidationResult result) { return result.getResults().values().stream() .anyMatch(r -> !r.getWarnings().isEmpty()); } } Advanced Policy Examples
1. Java-Specific Resource Policies
# policies/kubernetes/java_resources.rego package kubernetes.java_resources # Validate JVM heap settings match container resources deny[msg] { input.kind == "Deployment" container := input.spec.template.spec.containers[_] is_java_container(container) jvm_heap := get_jvm_heap_mb(container) container_memory := get_container_memory_mb(container) jvm_heap > container_memory * 0.8 msg := sprintf("JVM heap (%dMB) exceeds 80%% of container memory (%dMB) for container '%s'", [jvm_heap, container_memory, container.name]) } # Require appropriate JVM options for production deny[msg] { input.kind == "Deployment" container := input.spec.template.spec.containers[_] is_java_container(container) env := get_java_opts(container) not contains(env, "-XX:+UseContainerSupport") msg := sprintf("Java container '%s' must enable UseContainerSupport", [container.name]) } deny[msg] { input.kind == "Deployment" container := input.spec.template.spec.containers[_] is_java_container(container) env := get_java_opts(container) not contains(env, "-XX:+UseG1GC") msg := sprintf("Java container '%s' should use G1 garbage collector", [container.name]) } # Validate Spring Boot actuator configuration warn[msg] { input.kind == "Deployment" container := input.spec.template.spec.containers[_] is_java_container(container) not has_spring_actuator_probes(container) msg := sprintf("Java container '%s' should configure Spring Boot actuator health probes", [container.name]) } # Helper functions get_jvm_heap_mb(container) = heap { java_opts := get_java_opts(container) matches := regex.find_all_string_submatch_n("-Xmx([0-9]+)([MG])", java_opts, -1) count := to_number(matches[0][1]) unit := matches[0][2] heap = unit == "G" ? count * 1024 : count } get_container_memory_mb(container) = memory { memory_str := container.resources.limits.memory regex.match("^[0-9]+Mi$", memory_str) memory = to_number(trim(memory_str, "Mi")) } else = memory { memory_str := container.resources.limits.memory regex.match("^[0-9]+Gi$", memory_str) memory = to_number(trim(memory_str, "Gi")) * 1024 } get_java_opts(container) = opts { env := container.env[_] env.name == "JAVA_OPTS" opts = env.value } else = "" has_spring_actuator_probes(container) { container.livenessProbe.httpGet.path == "/actuator/health/liveness" container.readinessProbe.httpGet.path == "/actuator/health/readiness" } 2. Security Policies for Java Apps
# policies/kubernetes/security_java.rego package kubernetes.security_java # Require security context for Java apps deny[msg] { input.kind == "Deployment" container := input.spec.template.spec.containers[_] is_java_container(container) not container.securityContext.allowPrivilegeEscalation == false msg := sprintf("Java container '%s' must disable privilege escalation", [container.name]) } deny[msg] { input.kind == "Deployment" container := input.spec.template.spec.containers[_] is_java_container(container) not container.securityContext.capabilities.drop == ["ALL"] msg := sprintf("Java container '%s' must drop all capabilities", [container.name]) } # Validate network policies deny[msg] { input.kind == "Deployment" app := input.metadata.labels.app not has_network_policy(app) msg := sprintf("Deployment '%s' must have a network policy", [app]) } # Require Pod Security Standards deny[msg] { input.kind == "Deployment" spec := input.spec.template.spec spec.hostNetwork msg := "Deployment must not use host network" } deny[msg] { input.kind == "Deployment" spec := input.spec.template.spec spec.hostPID msg := "Deployment must not use host PID namespace" } Testing Policies
1. Policy Unit Tests
@Testcontainers @SpringBootTest class JavaPolicyValidationTest { @Container static GenericContainer conftestContainer = new GenericContainer("openpolicyagent/conftest:latest") .withCommand("sleep", "infinity"); @Autowired private ConftestService conftestService; @Test void shouldPassValidJavaDeployment() { String manifestPath = "src/test/resources/valid-java-deployment.yaml"; String policyPath = "policies/kubernetes"; PolicyValidationResult result = conftestService.validateKubernetesManifest( manifestPath, policyPath); assertTrue(result.isSuccess()); assertEquals(0, result.getViolations().size()); } @Test void shouldFailDeploymentWithoutResourceLimits() { String manifestPath = "src/test/resources/no-resource-limits.yaml"; String policyPath = "policies/kubernetes"; PolicyValidationResult result = conftestService.validateKubernetesManifest( manifestPath, policyPath); assertFalse(result.isSuccess()); assertTrue(result.getViolations().stream() .anyMatch(v -> v.getMessage().contains("resource limits"))); } @Test void shouldFailJavaContainerWithoutJVMSettings() { String manifestPath = "src/test/resources/no-jvm-settings.yaml"; String policyPath = "policies/kubernetes"; PolicyValidationResult result = conftestService.validateKubernetesManifest( manifestPath, policyPath); assertFalse(result.isSuccess()); assertTrue(result.getViolations().stream() .anyMatch(v -> v.getMessage().contains("JVM memory settings"))); } } 2. Test Data Examples
# src/test/resources/valid-java-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: user-service labels: app: user-service spec: replicas: 3 selector: matchLabels: app: user-service template: metadata: labels: app: user-service spec: securityContext: runAsNonRoot: true containers: - name: user-service image: eclipse-temurin:17-jre ports: - containerPort: 8080 env: - name: JAVA_OPTS value: "-Xmx512m -Xms256m -XX:+UseContainerSupport -XX:+UseG1GC" resources: requests: memory: "512Mi" cpu: "200m" limits: memory: "1Gi" cpu: "500m" securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL readOnlyRootFilesystem: true livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 60 periodSeconds: 10 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 30 periodSeconds: 5
Monitoring and Reporting
1. Validation Metrics
@Service @Slf4j public class PolicyValidationMetrics { private final MeterRegistry meterRegistry; private final Counter validationCounter; private final Timer validationTimer; public PolicyValidationMetrics(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; this.validationCounter = Counter.builder("policy.validation.total") .description("Total policy validations") .register(meterRegistry); this.validationTimer = Timer.builder("policy.validation.duration") .description("Policy validation duration") .register(meterRegistry); } public void recordValidation(String policyType, boolean success, long duration) { validationCounter.increment(); validationTimer.record(duration, TimeUnit.MILLISECONDS); meterRegistry.counter("policy.validation.result", "policy_type", policyType, "success", String.valueOf(success) ).increment(); } public void recordViolation(String policyName, String violationType) { meterRegistry.counter("policy.violation.total", "policy", policyName, "violation_type", violationType ).increment(); } } Conclusion
Conftest with Rego policies provides a powerful framework for validating Java application configurations with:
- Declarative Policies - Rego language for clear, maintainable rules
- Multi-Format Support - Kubernetes, Docker, JSON, YAML, and more
- Java Integration - Seamless integration with Java build tools and CI/CD
- Security Enforcement - Automated security and best practices validation
- Custom Rules - Tailored policies for Java-specific requirements
By implementing Conftest with the patterns shown above, Java teams can ensure consistent, secure, and compliant infrastructure configurations across their entire application stack.