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.