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.