Article
Gatekeeper is a Kubernetes-native policy controller that leverages Open Policy Agent (OPA) to enforce policies and constraints across your Kubernetes cluster. For Java applications running on Kubernetes, Gatekeeper provides a powerful way to enforce security, compliance, and operational policies at the infrastructure level, complementing application-level authorization.
In this guide, we'll explore how to implement Gatekeeper with OPA for Java applications, creating comprehensive policy enforcement across your Kubernetes ecosystem.
Why Gatekeeper/OPA for Java Applications?
- Kubernetes-Native: Designed specifically for Kubernetes environments
- Declarative Policies: Rego language for flexible policy definition
- Admission Control: Prevent non-compliant resources from being created
- Audit Functionality: Continuously monitor existing resources for compliance
- Multi-Tenant Security: Enforce namespace isolation and resource quotas
- Compliance as Code: Version control and automate compliance requirements
Part 1: Architecture Overview
1.1 Gatekeeper/OPA Components
Java Application → Kubernetes API → Gatekeeper Webhook → OPA Engine → Policy Decision ↓ ↓ ↓ ↓ ↓ Deployment Validation Admission Rego Policies Allow/Deny ConfigMap Request Controller Constraint Audit Results Secret Templates
1.2 Policy Enforcement Points
Pre-deployment: kubectl apply → Gatekeeper → Allow/Deny Runtime: Gatekeeper Audit → Compliance Status Post-deployment: Monitoring → Violation Reports
1.3 Project Structure
java-opa-gatekeeper/ ├── src/ │ ├── main/java/com/example/ │ └── resources/ ├── policies/ │ ├── constraints/ │ ├── templates/ │ └── lib/ ├── kubernetes/ │ ├── gatekeeper/ │ ├── application/ │ └── policies/ └── docker/ └── Dockerfile
Part 2: Dependencies and Setup
2.1 Maven Dependencies
<!-- pom.xml --> <properties> <spring-boot.version>3.1.0</spring-boot.version> <opa.version>0.61.0</opa.version> <kubernetes-client.version>6.7.2</kubernetes-client.version> </properties> <dependencies> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${spring-boot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> <version>${spring-boot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> <version>${spring-boot.version}</version> </dependency> <!-- OPA Java Client --> <dependency> <groupId>com.openpolicyagent</groupId> <artifactId>opa-client</artifactId> <version>${opa.version}</version> </dependency> <!-- Kubernetes Client --> <dependency> <groupId>io.kubernetes</groupId> <artifactId>client-java</artifactId> <version>${kubernetes-client.version}</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> <!-- Testing --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>${spring-boot.version}</version> <scope>test</scope> </dependency> </dependencies> Part 3: Gatekeeper Installation and Configuration
3.1 Gatekeeper Installation
# kubernetes/gatekeeper/installation.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: configs.config.gatekeeper.sh spec: group: config.gatekeeper.sh names: kind: Config listKind: ConfigList plural: configs singular: config scope: Cluster versions: - name: v1alpha1 served: true storage: true schema: openAPIV3Schema: type: object properties: apiVersion: type: string kind: type: string metadata: type: object spec: type: object properties: sync: type: object properties: syncOnly: type: array items: type: object properties: group: type: string version: type: string kind: type: string --- apiVersion: apps/v1 kind: Deployment metadata: name: gatekeeper-controller-manager namespace: gatekeeper-system labels: control-plane: controller-manager gatekeeper.sh/system: "yes" spec: replicas: 2 selector: matchLabels: control-plane: controller-manager gatekeeper.sh/system: "yes" template: metadata: labels: control-plane: controller-manager gatekeeper.sh/system: "yes" spec: containers: - args: - --port=8443 - --logtostderr - --exempt-namespace=gatekeeper-system - --operation=webhook - --operation=audit - --operation=status - --audit-interval=60 command: - /manager image: openpolicyagent/gatekeeper:v3.13.0 name: manager ports: - containerPort: 8443 protocol: TCP - containerPort: 9090 protocol: TCP - containerPort: 8888 protocol: TCP - containerPort: 8080 protocol: TCP resources: limits: cpu: 1000m memory: 512Mi requests: cpu: 100m memory: 256Mi - args: - --enable-pprof - --metrics-addr=:8888 - --audit-match-kind-only - --logtostderr - --exempt-namespace=gatekeeper-system - --operation=webhook - --audit-interval=60 command: - /gatekeeper image: openpolicyagent/gatekeeper:v3.13.0 name: gatekeeper ports: - containerPort: 8888 protocol: TCP resources: limits: cpu: 1000m memory: 512Mi requests: cpu: 100m memory: 256Mi serviceAccountName: gatekeeper-admin --- apiVersion: v1 kind: Service metadata: name: gatekeeper-webhook-service namespace: gatekeeper-system labels: gatekeeper.sh/system: "yes" spec: ports: - port: 443 targetPort: 8443 selector: control-plane: controller-manager gatekeeper.sh/system: "yes"
3.2 Gatekeeper Configuration
# kubernetes/gatekeeper/config.yaml apiVersion: config.gatekeeper.sh/v1alpha1 kind: Config metadata: name: config namespace: gatekeeper-system spec: sync: syncOnly: - group: "" version: "v1" kind: "Namespace" - group: "" version: "v1" kind: "Pod" - group: "apps" version: "v1" kind: "Deployment" - group: "apps" version: "v1" kind: "StatefulSet" - group: "" version: "v1" kind: "Service" - group: "" version: "v1" kind: "ConfigMap" - group: "" version: "v1" kind: "Secret" - group: "batch" version: "v1" kind: "Job" - group: "networking.k8s.io" version: "v1" kind: "Ingress"
Part 4: Policy Definitions
4.1 Constraint Templates
# policies/templates/required-labels.yaml apiVersion: templates.gatekeeper.sh/v1 kind: ConstraintTemplate metadata: name: k8srequiredlabels spec: crd: spec: names: kind: K8sRequiredLabels validation: openAPIV3Schema: type: object properties: labels: type: array items: type: string targets: - target: admission.k8s.gatekeeper.sh rego: | package k8srequiredlabels violation[{"msg": msg, "details": {"missing_labels": missing}}] { provided := {label | input.review.object.metadata.labels[label]} required := {label | label := input.parameters.labels[_]} missing := required - provided count(missing) > 0 msg := sprintf("you must provide labels: %v", [missing]) } --- # policies/templates/container-resources.yaml apiVersion: templates.gatekeeper.sh/v1 kind: ConstraintTemplate metadata: name: k8scontainerresources spec: crd: spec: names: kind: K8sContainerResources validation: openAPIV3Schema: type: object properties: limits: type: object properties: cpu: type: string memory: type: string requests: type: object properties: cpu: type: string memory: type: string targets: - target: admission.k8s.gatekeeper.sh rego: | package k8scontainerresources violation[{"msg": msg}] { container := input.review.object.spec.template.spec.containers[_] not container.resources.limits.cpu msg := sprintf("container %v must have CPU limits", [container.name]) } violation[{"msg": msg}] { container := input.review.object.spec.template.spec.containers[_] not container.resources.limits.memory msg := sprintf("container %v must have memory limits", [container.name]) } violation[{"msg": msg}] { container := input.review.object.spec.template.spec.containers[_] not container.resources.requests.cpu msg := sprintf("container %v must have CPU requests", [container.name]) } violation[{"msg": msg}] { container := input.review.object.spec.template.spec.containers[_] not container.resources.requests.memory msg := sprintf("container %v must have memory requests", [container.name]) } --- # policies/templates/java-specific.yaml apiVersion: templates.gatekeeper.sh/v1 kind: ConstraintTemplate metadata: name: javaspecificrequirements spec: crd: spec: names: kind: JavaSpecificRequirements validation: openAPIV3Schema: type: object properties: requireLivenessProbe: type: boolean requireReadinessProbe: type: boolean requireSecurityContext: type: boolean allowedJavaVersions: type: array items: type: string targets: - target: admission.k8s.gatekeeper.sh rego: | package javaspecificrequirements violation[{"msg": msg}] { input.parameters.requireLivenessProbe not input.review.object.spec.template.spec.containers[_].livenessProbe msg := "Java applications must have liveness probe" } violation[{"msg": msg}] { input.parameters.requireReadinessProbe not input.review.object.spec.template.spec.containers[_].readinessProbe msg := "Java applications must have readiness probe" } violation[{"msg": msg}] { input.parameters.requireSecurityContext not input.review.object.spec.template.spec.containers[_].securityContext msg := "Java applications must have security context" } violation[{"msg": msg}] { allowed_versions := input.parameters.allowedJavaVersions container := input.review.object.spec.template.spec.containers[_] image := container.image not java_version_allowed(image, allowed_versions) msg := sprintf("Java version in image %v is not allowed. Allowed versions: %v", [image, allowed_versions]) } java_version_allowed(image, allowed_versions) { version := get_java_version(image) version == allowed_versions[_] } get_java_version(image) = version { contains(image, "openjdk:") parts := split(image, ":") version := parts[1] } get_java_version(image) = version { contains(image, "java:") parts := split(image, ":") version := parts[1] } 4.2 Constraints
# policies/constraints/required-labels.yaml apiVersion: constraints.gatekeeper.sh/v1 kind: K8sRequiredLabels metadata: name: must-have-labels spec: match: kinds: - apiGroups: ["apps"] kinds: ["Deployment", "StatefulSet"] - apiGroups: [""] kinds: ["Pod"] parameters: labels: - "app.kubernetes.io/name" - "app.kubernetes.io/version" - "app.kubernetes.io/environment" --- # policies/constraints/java-app-resources.yaml apiVersion: constraints.gatekeeper.sh/v1 kind: K8sContainerResources metadata: name: java-app-resources spec: match: kinds: - apiGroups: ["apps"] kinds: ["Deployment", "StatefulSet"] namespaces: - "default" - "production" - "staging" parameters: limits: cpu: "1000m" memory: "1Gi" requests: cpu: "100m" memory: "256Mi" --- # policies/constraints/java-requirements.yaml apiVersion: constraints.gatekeeper.sh/v1 kind: JavaSpecificRequirements metadata: name: java-app-requirements spec: match: kinds: - apiGroups: ["apps"] kinds: ["Deployment"] labelSelector: matchExpressions: - key: "app.kubernetes.io/component" operator: "In" values: ["java-app"] parameters: requireLivenessProbe: true requireReadinessProbe: true requireSecurityContext: true allowedJavaVersions: - "17" - "21" - "17-jre" - "21-jre" --- # policies/constraints/security-context.yaml apiVersion: constraints.gatekeeper.sh/v1 kind: K8sPSP metadata: name: java-app-security spec: match: kinds: - apiGroups: ["apps"] kinds: ["Deployment", "StatefulSet"] parameters: runAsNonRoot: true allowPrivilegeEscalation: false requiredDropCapabilities: - "ALL"
4.3 Custom Java Application Policies
# policies/templates/java-app-config.yaml apiVersion: templates.gatekeeper.sh/v1 kind: ConstraintTemplate metadata: name: javaappconfig spec: crd: spec: names: kind: JavaAppConfig validation: openAPIV3Schema: type: object properties: requireActuatorEndpoints: type: boolean requireResourceLimits: type: boolean maxMemoryLimit: type: string targets: - target: admission.k8s.gatekeeper.sh rego: | package javaappconfig violation[{"msg": msg}] { input.parameters.requireActuatorEndpoints container := input.review.object.spec.template.spec.containers[_] env_vars := {name | container.env[name]} not env_vars["MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE"] msg := "Java applications must expose actuator endpoints" } violation[{"msg": msg}] { input.parameters.requireResourceLimits container := input.review.object.spec.template.spec.containers[_] not container.resources.limits.memory msg := "Java applications must have memory limits" } violation[{"msg": msg}] { input.parameters.maxMemoryLimit != "" container := input.review.object.spec.template.spec.containers[_] memory_limit := container.resources.limits.memory parse_memory(memory_limit) > parse_memory(input.parameters.maxMemoryLimit) msg := sprintf("Memory limit %v exceeds maximum allowed %v", [memory_limit, input.parameters.maxMemoryLimit]) } parse_memory(memory) = number { regex.find_n("^([0-9]+)", memory, 1) number := to_number(regex.find_n("^([0-9]+)", memory, 1)[0]) } parse_memory(memory) = number { regex.find_n("^([0-9]+)Mi$", memory, 1) number := to_number(regex.find_n("^([0-9]+)Mi$", memory, 1)[0]) } parse_memory(memory) = number { regex.find_n("^([0-9]+)Gi$", memory, 1) number := to_number(regex.find_n("^([0-9]+)Gi$", memory, 1)[0]) * 1024 } Part 5: Java Application Implementation
5.1 Policy-Aware Java Application
// File: src/main/java/com/example/PolicyAwareApplication.java package com.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; @SpringBootApplication @EnableConfigurationProperties({AppProperties.class, PolicyConfig.class}) public class PolicyAwareApplication { public static void main(String[] args) { SpringApplication.run(PolicyAwareApplication.class, args); } } // File: src/main/java/com/example/AppProperties.java package com.example; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "app") public record AppProperties( String name, String version, String environment, ResourceConstraints resources, SecurityConfig security ) { public record ResourceConstraints( String memoryLimit, String cpuLimit, String memoryRequest, String cpuRequest ) {} public record SecurityConfig( boolean runAsNonRoot, boolean readOnlyRootFilesystem, String serviceAccount ) {} } // File: src/main/java/com/example/PolicyConfig.java package com.example; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "app.policy") public record PolicyConfig( boolean enforceResourceLimits, boolean requireSecurityContext, boolean requireProbes, ComplianceRequirements compliance ) { public record ComplianceRequirements( boolean hipaa, boolean pci, boolean gdpr, String securityStandard ) {} } 5.2 Policy Validation Service
// File: src/main/java/com/example/service/PolicyValidationService.java package com.example.service; import com.example.AppProperties; import com.example.PolicyConfig; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.net.URI; import java.util.Map; import java.util.concurrent.CompletableFuture; @Service public class PolicyValidationService { private static final Logger logger = LoggerFactory.getLogger(PolicyValidationService.class); private final HttpClient httpClient; private final ObjectMapper objectMapper; private final AppProperties appProperties; private final PolicyConfig policyConfig; public PolicyValidationService(AppProperties appProperties, PolicyConfig policyConfig) { this.httpClient = HttpClient.newHttpClient(); this.objectMapper = new ObjectMapper(); this.appProperties = appProperties; this.policyConfig = policyConfig; } public CompletableFuture<PolicyDecision> validateDeploymentSpec(DeploymentSpec spec) { return validateWithOPA("deployment/validate", spec) .thenApply(this::mapToPolicyDecision); } public CompletableFuture<PolicyDecision> validateConfigMap(ConfigMapSpec spec) { return validateWithOPA("configmap/validate", spec) .thenApply(this::mapToPolicyDecision); } public CompletableFuture<PolicyDecision> validateService(ServiceSpec spec) { return validateWithOPA("service/validate", spec) .thenApply(this::mapToPolicyDecision); } public CompletableFuture<ComplianceReport> getComplianceStatus() { Map<String, Object> input = Map.of( "application", appProperties.name(), "environment", appProperties.environment(), "resources", appProperties.resources(), "security", appProperties.security(), "policies", policyConfig ); return validateWithOPA("compliance/check", input) .thenApply(response -> { try { return objectMapper.convertValue(response, ComplianceReport.class); } catch (Exception e) { logger.error("Failed to parse compliance report", e); return new ComplianceReport(false, "Failed to parse compliance report", Map.of()); } }); } private CompletableFuture<Map<String, Object>> validateWithOPA(String path, Object input) { try { String requestBody = objectMapper.writeValueAsString(Map.of("input", input)); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://localhost:8181/v1/data/" + path)) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(requestBody)) .build(); return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(response -> { try { return objectMapper.readValue(response.body(), Map.class); } catch (Exception e) { logger.error("Failed to parse OPA response", e); return Map.of("result", Map.of("allowed", false, "reason", "Parse error")); } }); } catch (Exception e) { logger.error("Failed to validate with OPA", e); return CompletableFuture.completedFuture( Map.of("result", Map.of("allowed", false, "reason", "Validation error"))); } } private PolicyDecision mapToPolicyDecision(Map<String, Object> opaResponse) { try { Map<String, Object> result = (Map<String, Object>) opaResponse.get("result"); boolean allowed = (Boolean) result.getOrDefault("allowed", false); String reason = (String) result.getOrDefault("reason", "No reason provided"); Map<String, Object> details = (Map<String, Object>) result.getOrDefault("details", Map.of()); return new PolicyDecision(allowed, reason, details); } catch (Exception e) { logger.error("Failed to map OPA response to policy decision", e); return new PolicyDecision(false, "Mapping error", Map.of()); } } // Data classes public record DeploymentSpec( String name, String namespace, Map<String, String> labels, Map<String, String> annotations, ContainerSpec container, ResourceRequirements resources, SecurityContext securityContext, Probe livenessProbe, Probe readinessProbe ) {} public record ContainerSpec( String image, String[] command, Map<String, String> env, int[] ports ) {} public record ResourceRequirements( String memoryLimit, String cpuLimit, String memoryRequest, String cpuRequest ) {} public record SecurityContext( boolean runAsNonRoot, Long runAsUser, boolean readOnlyRootFilesystem, String[] capabilities ) {} public record Probe( String type, // http, tcp, exec int initialDelaySeconds, int periodSeconds, Map<String, Object> config ) {} public record ConfigMapSpec( String name, String namespace, Map<String, String> data, Map<String, String> labels ) {} public record ServiceSpec( String name, String namespace, String type, Map<String, String> selector, int[] ports, Map<String, String> labels ) {} public record PolicyDecision( boolean allowed, String reason, Map<String, Object> details ) {} public record ComplianceReport( boolean compliant, String summary, Map<String, Object> violations ) {} } 5.3 Policy Enforcement Controller
// File: src/main/java/com/example/controller/PolicyController.java package com.example.controller; import com.example.service.PolicyValidationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.Map; import java.util.concurrent.CompletableFuture; @RestController @RequestMapping("/api/v1/policies") public class PolicyController { private static final Logger logger = LoggerFactory.getLogger(PolicyController.class); private final PolicyValidationService policyValidationService; public PolicyController(PolicyValidationService policyValidationService) { this.policyValidationService = policyValidationService; } @PostMapping("/validate/deployment") public CompletableFuture<ResponseEntity<PolicyValidationService.PolicyDecision>> validateDeployment( @RequestBody PolicyValidationService.DeploymentSpec deploymentSpec) { logger.info("Validating deployment: {}", deploymentSpec.name()); return policyValidationService.validateDeploymentSpec(deploymentSpec) .thenApply(decision -> { if (decision.allowed()) { logger.info("Deployment validation passed: {}", deploymentSpec.name()); return ResponseEntity.ok(decision); } else { logger.warn("Deployment validation failed: {} - {}", deploymentSpec.name(), decision.reason()); return ResponseEntity.badRequest().body(decision); } }); } @GetMapping("/compliance") public CompletableFuture<ResponseEntity<PolicyValidationService.ComplianceReport>> getCompliance() { return policyValidationService.getComplianceStatus() .thenApply(ResponseEntity::ok); } @PostMapping("/validate/configmap") public CompletableFuture<ResponseEntity<PolicyValidationService.PolicyDecision>> validateConfigMap( @RequestBody PolicyValidationService.ConfigMapSpec configMapSpec) { return policyValidationService.validateConfigMap(configMapSpec) .thenApply(decision -> { if (decision.allowed()) { return ResponseEntity.ok(decision); } else { return ResponseEntity.badRequest().body(decision); } }); } @GetMapping("/health") public ResponseEntity<Map<String, String>> health() { return ResponseEntity.ok(Map.of( "status", "healthy", "service", "policy-controller", "policyEnforcement", "enabled" )); } } Part 6: Kubernetes Manifests with Policy Compliance
6.1 Policy-Compliant Java Application Deployment
# kubernetes/application/java-app-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: java-policy-app namespace: default labels: app.kubernetes.io/name: java-policy-app app.kubernetes.io/version: "1.0.0" app.kubernetes.io/environment: production app.kubernetes.io/component: java-app app.kubernetes.io/part-of: policy-demo spec: replicas: 3 selector: matchLabels: app.kubernetes.io/name: java-policy-app template: metadata: labels: app.kubernetes.io/name: java-policy-app app.kubernetes.io/version: "1.0.0" app.kubernetes.io/environment: production annotations: prometheus.io/scrape: "true" prometheus.io/port: "8080" prometheus.io/path: "/actuator/prometheus" spec: serviceAccountName: java-app-service-account securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 1000 containers: - name: java-app image: openjdk:17-jre-slim command: ["java", "-jar", "/app/app.jar"] ports: - containerPort: 8080 name: http env: - name: SPRING_PROFILES_ACTIVE value: "production" - name: MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE value: "health,info,metrics,prometheus" - name: JAVA_OPTS value: "-Xmx512m -Xms256m -XX:+UseG1GC" resources: limits: memory: "512Mi" cpu: "500m" requests: memory: "256Mi" cpu: "100m" livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 3 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 1000 capabilities: drop: - ALL volumeMounts: - name: tmp mountPath: /tmp volumes: - name: tmp emptyDir: {} --- apiVersion: v1 kind: Service metadata: name: java-policy-app namespace: default labels: app.kubernetes.io/name: java-policy-app app.kubernetes.io/version: "1.0.0" spec: ports: - port: 80 targetPort: 8080 protocol: TCP name: http selector: app.kubernetes.io/name: java-policy-app type: ClusterIP --- apiVersion: v1 kind: ServiceAccount metadata: name: java-app-service-account namespace: default labels: app.kubernetes.io/name: java-policy-app 6.2 ConfigMap with Policy Validation
# kubernetes/application/java-app-config.yaml apiVersion: v1 kind: ConfigMap metadata: name: java-app-config namespace: default labels: app.kubernetes.io/name: java-policy-app app.kubernetes.io/component: config data: application.yaml: | app: name: java-policy-app version: 1.0.0 environment: production resources: memoryLimit: 512Mi cpuLimit: 500m memoryRequest: 256Mi cpuRequest: 100m security: runAsNonRoot: true readOnlyRootFilesystem: true serviceAccount: java-app-service-account policy: enforceResourceLimits: true requireSecurityContext: true requireProbes: true compliance: hipaa: false pci: false gdpr: true securityStandard: basic spring: application: name: java-policy-app management: endpoints: web: exposure: include: health,info,metrics,prometheus endpoint: health: show-details: always
Part 7: Testing and Validation
7.1 Policy Testing
// File: src/test/java/com/example/service/PolicyValidationServiceTest.java package com.example.service; import com.example.AppProperties; import com.example.PolicyConfig; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.util.Map; import java.util.concurrent.CompletableFuture; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class PolicyValidationServiceTest { @Mock private AppProperties appProperties; @Mock private PolicyConfig policyConfig; private PolicyValidationService policyValidationService; @BeforeEach void setUp() { policyValidationService = new PolicyValidationService(appProperties, policyConfig); } @Test void shouldValidateDeploymentSuccessfully() { // Given PolicyValidationService.DeploymentSpec deploymentSpec = new PolicyValidationService.DeploymentSpec( "test-app", "default", Map.of("app", "test"), Map.of(), new PolicyValidationService.ContainerSpec( "openjdk:17-jre", new String[]{"java", "-jar", "app.jar"}, Map.of("JAVA_OPTS", "-Xmx512m"), new int[]{8080} ), new PolicyValidationService.ResourceRequirements( "512Mi", "500m", "256Mi", "100m" ), new PolicyValidationService.SecurityContext( true, 1000L, true, new String[]{} ), new PolicyValidationService.Probe("http", 30, 10, Map.of()), new PolicyValidationService.Probe("http", 5, 5, Map.of()) ); // When CompletableFuture<PolicyValidationService.PolicyDecision> result = policyValidationService.validateDeploymentSpec(deploymentSpec); // Then - This would typically mock the HTTP call to OPA assertNotNull(result); } } 7.2 OPA Policy Testing
# policies/test/deployment_test.rego package deployment.test import data.deployment.validate test_valid_deployment { valid_input := { "name": "test-app", "namespace": "default", "labels": { "app.kubernetes.io/name": "test-app", "app.kubernetes.io/version": "1.0.0" }, "container": { "image": "openjdk:17-jre", "resources": { "limits": { "memory": "512Mi", "cpu": "500m" }, "requests": { "memory": "256Mi", "cpu": "100m" } } } } decision := validate with input as valid_input decision.allowed == true } test_invalid_deployment_missing_labels { invalid_input := { "name": "test-app", "namespace": "default", "labels": {}, "container": { "image": "openjdk:17-jre", "resources": { "limits": { "memory": "512Mi", "cpu": "500m" } } } } decision := validate with input as invalid_input decision.allowed == false decision.reason == "you must provide labels: {\"app.kubernetes.io/name\", \"app.kubernetes.io/version\"}" } Part 8: Monitoring and Auditing
8.1 Policy Audit Dashboard
// File: src/main/java/com/example/controller/PolicyAuditController.java package com.example.controller; import com.example.service.PolicyValidationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @RestController @RequestMapping("/api/v1/audit") public class PolicyAuditController { private static final Logger logger = LoggerFactory.getLogger(PolicyAuditController.class); private final PolicyValidationService policyValidationService; private final Map<String, AuditRecord> auditLog = new ConcurrentHashMap<>(); public PolicyAuditController(PolicyValidationService policyValidationService) { this.policyValidationService = policyValidationService; } @GetMapping("/violations") public ResponseEntity<List<AuditRecord>> getRecentViolations() { List<AuditRecord> violations = auditLog.values().stream() .filter(record -> !record.allowed()) .sorted(Comparator.comparing(AuditRecord::timestamp).reversed()) .limit(100) .toList(); return ResponseEntity.ok(violations); } @GetMapping("/compliance/status") public ResponseEntity<ComplianceStatus> getComplianceStatus() { long totalDecisions = auditLog.size(); long violations = auditLog.values().stream() .filter(record -> !record.allowed()) .count(); double complianceRate = totalDecisions > 0 ? (double) (totalDecisions - violations) / totalDecisions * 100 : 100.0; ComplianceStatus status = new ComplianceStatus( complianceRate, totalDecisions, violations, LocalDateTime.now() ); return ResponseEntity.ok(status); } @PostMapping("/record") public ResponseEntity<Void> recordDecision(@RequestBody AuditRecord record) { String id = UUID.randomUUID().toString(); auditLog.put(id, record); logger.info("Recorded audit decision: {} - {}", record.resourceType(), record.allowed()); return ResponseEntity.ok().build(); } // Data classes public record AuditRecord( String id, String resourceType, String resourceName, String namespace, boolean allowed, String reason, Map<String, Object> details, LocalDateTime timestamp, String user ) {} public record ComplianceStatus( double complianceRate, long totalDecisions, long violations, LocalDateTime lastUpdated ) {} } Best Practices for Gatekeeper with Java
- Start Simple: Begin with basic policies and gradually add complexity
- Test Policies: Write comprehensive tests for your Rego policies
- Monitor Violations: Set up alerts for policy violations
- Version Control: Store policies in version control with your application code
- Document Policies: Maintain clear documentation for each policy
- Gradual Rollout: Use dry-run mode before enforcing policies
- Combine with App-Level: Use both infrastructure and application-level policies
- Regular Audits: Continuously monitor and update policies
Conclusion
Implementing Gatekeeper with OPA for Java applications provides a robust policy enforcement framework that operates at the Kubernetes infrastructure level. By combining Gatekeeper's admission control with Java application-level validation, you can create a comprehensive security and compliance posture that:
- Prevents misconfigurations before they reach production
- Enforces organizational standards across all deployments
- Provides audit trails for compliance requirements
- Automates security best practices for Java applications
- Integrates seamlessly with existing CI/CD pipelines
The patterns and examples in this guide demonstrate how to effectively implement policy-as-code for Java applications running on Kubernetes, creating a secure, compliant, and maintainable infrastructure that scales with your organization's needs.