Policy-Driven Kubernetes: Implementing Gatekeeper Constraints in Java Applications

Gatekeeper is a Kubernetes admission controller that enforces custom policies using the Open Policy Agent (OPA) framework. While Gatekeeper itself is written in Go, Java applications need to understand and work with Gatekeeper constraints to ensure compliance and proper deployment in Kubernetes environments. Let's explore how Java developers can implement, test, and validate applications against Gatekeeper policies.

Understanding Gatekeeper Components

Key Concepts:

  • Constraints - Specific policy requirements that must be satisfied
  • Constraint Templates - Reusable policy definitions
  • Config - Gatekeeper configuration and resource exclusions
  • Audit - Continuous evaluation of existing resources

Java Client for Gatekeeper Constraints

1. Dependencies Setup

<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>io.kubernetes</groupId>
<artifactId>client-java</artifactId>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.15.2</version>
</dependency>
</dependencies>

2. Gatekeeper Constraint Models

public class GatekeeperConstraint {
private String apiVersion = "constraints.gatekeeper.sh/v1beta1";
private String kind;
private Metadata metadata;
private ConstraintSpec spec;
// Constructors, getters, setters
public GatekeeperConstraint() {}
public GatekeeperConstraint(String kind, String name, ConstraintSpec spec) {
this.kind = kind;
this.metadata = new Metadata(name);
this.spec = spec;
}
public static class Metadata {
private String name;
public Metadata() {}
public Metadata(String name) {
this.name = name;
}
// getters and setters
}
public static class ConstraintSpec {
private ConstraintMatch match;
private Map<String, Object> parameters;
private String enforcementAction = "deny";
// Constructors, getters, setters
public ConstraintSpec() {}
public ConstraintSpec(ConstraintMatch match, Map<String, Object> parameters) {
this.match = match;
this.parameters = parameters;
}
}
public static class ConstraintMatch {
private List<String> kinds;
private List<String> apiGroups;
private Map<String, String> namespaces;
private Map<String, String> excludedNamespaces;
// Constructors, getters, setters
public ConstraintMatch() {}
public ConstraintMatch(List<String> kinds, List<String> apiGroups) {
this.kinds = kinds;
this.apiGroups = apiGroups;
}
}
}

Validating Kubernetes Resources in Java

1. Resource Validation Service

@Service
public class KubernetesResourceValidator {
private final ObjectMapper yamlMapper;
private final KubernetesClient kubernetesClient;
private final GatekeeperService gatekeeperService;
public KubernetesResourceValidator(GatekeeperService gatekeeperService) {
this.gatekeeperService = gatekeeperService;
this.yamlMapper = new ObjectMapper(new YAMLFactory());
this.kubernetesClient = new DefaultKubernetesClient();
}
public ValidationResult validateDeployment(Deployment deployment) {
List<ConstraintViolation> violations = new ArrayList<>();
// Check against common Gatekeeper constraints
violations.addAll(validateResourceLimits(deployment));
violations.addAll(validateSecurityContext(deployment));
violations.addAll(validateImageSources(deployment));
violations.addAll(validateLabels(deployment));
return new ValidationResult(violations);
}
private List<ConstraintViolation> validateResourceLimits(Deployment deployment) {
List<ConstraintViolation> violations = new ArrayList<>();
deployment.getSpec().getTemplate().getSpec().getContainers()
.forEach(container -> {
if (container.getResources() == null) {
violations.add(new ConstraintViolation(
"K8sRequiredResources",
"Container must have resource limits and requests",
container.getName()
));
} else {
if (container.getResources().getLimits() == null) {
violations.add(new ConstraintViolation(
"K8sRequiredLimits",
"Container must have resource limits",
container.getName()
));
}
if (container.getResources().getRequests() == null) {
violations.add(new ConstraintViolation(
"K8sRequiredRequests", 
"Container must have resource requests",
container.getName()
));
}
}
});
return violations;
}
private List<ConstraintViolation> validateSecurityContext(Deployment deployment) {
List<ConstraintViolation> violations = new ArrayList<>();
deployment.getSpec().getTemplate().getSpec().getContainers()
.forEach(container -> {
if (container.getSecurityContext() == null) {
violations.add(new ConstraintViolation(
"K8sRequiredSecurityContext",
"Container must have security context",
container.getName()
));
} else {
// Check for root user
if (container.getSecurityContext().getRunAsUser() != null &&
container.getSecurityContext().getRunAsUser() == 0) {
violations.add(new ConstraintViolation(
"K8sNoRootContainers",
"Container must not run as root user",
container.getName()
));
}
// Check for privileged mode
if (Boolean.TRUE.equals(container.getSecurityContext().getPrivileged())) {
violations.add(new ConstraintViolation(
"K8sNoPrivilegedContainers",
"Container must not run in privileged mode",
container.getName()
));
}
}
});
return violations;
}
private List<ConstraintViolation> validateImageSources(Deployment deployment) {
List<ConstraintViolation> violations = new ArrayList<>();
deployment.getSpec().getTemplate().getSpec().getContainers()
.forEach(container -> {
String image = container.getImage();
// Check for latest tag
if (image != null && image.endsWith(":latest")) {
violations.add(new ConstraintViolation(
"K8sNoLatestTag",
"Container image must not use latest tag",
container.getName()
));
}
// Check for approved registries
if (image != null && !isApprovedRegistry(image)) {
violations.add(new ConstraintViolation(
"K8sApprovedRegistries",
"Container image must come from approved registry",
container.getName()
));
}
});
return violations;
}
private List<ConstraintViolation> validateLabels(Deployment deployment) {
List<ConstraintViolation> violations = new ArrayList<>();
Map<String, String> labels = deployment.getMetadata().getLabels();
// Check required labels
if (labels == null || !labels.containsKey("app.kubernetes.io/name")) {
violations.add(new ConstraintViolation(
"K8sRequiredLabels",
"Deployment must have app.kubernetes.io/name label",
deployment.getMetadata().getName()
));
}
if (labels == null || !labels.containsKey("app.kubernetes.io/version")) {
violations.add(new ConstraintViolation(
"K8sRequiredLabels",
"Deployment must have app.kubernetes.io/version label", 
deployment.getMetadata().getName()
));
}
return violations;
}
private boolean isApprovedRegistry(String image) {
List<String> approvedRegistries = Arrays.asList(
"docker.io/your-org/",
"gcr.io/your-project/",
"registry.example.com/"
);
return approvedRegistries.stream().anyMatch(image::startsWith);
}
public static class ValidationResult {
private final List<ConstraintViolation> violations;
private final boolean valid;
public ValidationResult(List<ConstraintViolation> violations) {
this.violations = violations;
this.valid = violations.isEmpty();
}
// Getters
public List<ConstraintViolation> getViolations() { return violations; }
public boolean isValid() { return valid; }
}
public static class ConstraintViolation {
private final String constraint;
private final String message;
private final String resource;
public ConstraintViolation(String constraint, String message, String resource) {
this.constraint = constraint;
this.message = message;
this.resource = resource;
}
// Getters
public String getConstraint() { return constraint; }
public String getMessage() { return message; }
public String getResource() { return resource; }
}
}

Generating Compliant Kubernetes Manifests

1. Deployment Builder with Constraints

@Component
public class CompliantDeploymentBuilder {
private final KubernetesResourceValidator validator;
public CompliantDeploymentBuilder(KubernetesResourceValidator validator) {
this.validator = validator;
}
public Deployment buildCompliantDeployment(DeploymentSpec spec) {
Deployment deployment = createBaseDeployment(spec);
// Apply security constraints
applySecurityConstraints(deployment);
applyResourceConstraints(deployment);
applyLabelConstraints(deployment);
// Validate against Gatekeeper constraints
ValidationResult result = validator.validateDeployment(deployment);
if (!result.isValid()) {
throw new ConstraintViolationException(
"Deployment violates Gatekeeper constraints", result.getViolations());
}
return deployment;
}
private Deployment createBaseDeployment(DeploymentSpec spec) {
return new DeploymentBuilder()
.withNewMetadata()
.withName(spec.getName())
.withNamespace(spec.getNamespace())
.withLabels(createStandardLabels(spec))
.endMetadata()
.withNewSpec()
.withReplicas(spec.getReplicas())
.withNewTemplate()
.withNewMetadata()
.withLabels(createStandardLabels(spec))
.endMetadata()
.withNewSpec()
.withContainers(createContainers(spec))
.endSpec()
.endTemplate()
.endSpec()
.build();
}
private void applySecurityConstraints(Deployment deployment) {
deployment.getSpec().getTemplate().getSpec()
.setSecurityContext(createPodSecurityContext());
deployment.getSpec().getTemplate().getSpec().getContainers()
.forEach(container -> 
container.setSecurityContext(createContainerSecurityContext()));
}
private void applyResourceConstraints(Deployment deployment) {
deployment.getSpec().getTemplate().getSpec().getContainers()
.forEach(container -> 
container.setResources(createResourceRequirements()));
}
private void applyLabelConstraints(Deployment deployment) {
Map<String, String> labels = deployment.getMetadata().getLabels();
if (labels == null) {
labels = new HashMap<>();
}
// Add required labels
labels.putIfAbsent("app.kubernetes.io/name", deployment.getMetadata().getName());
labels.putIfAbsent("app.kubernetes.io/version", "1.0.0");
labels.putIfAbsent("app.kubernetes.io/managed-by", "java-application");
deployment.getMetadata().setLabels(labels);
}
private Map<String, String> createStandardLabels(DeploymentSpec spec) {
Map<String, String> labels = new HashMap<>();
labels.put("app.kubernetes.io/name", spec.getName());
labels.put("app.kubernetes.io/version", spec.getVersion());
labels.put("app.kubernetes.io/component", spec.getComponent());
labels.put("app.kubernetes.io/part-of", spec.getApplication());
return labels;
}
private List<Container> createContainers(DeploymentSpec spec) {
return spec.getContainers().stream()
.map(this::createContainer)
.collect(Collectors.toList());
}
private Container createContainer(ContainerSpec spec) {
return new ContainerBuilder()
.withName(spec.getName())
.withImage(spec.getImage())
.withPorts(createContainerPorts(spec))
.withEnv(createEnvironmentVariables(spec))
.withResources(createResourceRequirements())
.withSecurityContext(createContainerSecurityContext())
.build();
}
private io.fabric8.kubernetes.api.model.SecurityContext createPodSecurityContext() {
return new io.fabric8.kubernetes.api.model.SecurityContextBuilder()
.withRunAsNonRoot(true)
.withRunAsUser(1000L)
.withRunAsGroup(3000L)
.withFsGroup(2000L)
.build();
}
private io.fabric8.kubernetes.api.model.SecurityContext createContainerSecurityContext() {
return new io.fabric8.kubernetes.api.model.SecurityContextBuilder()
.withRunAsNonRoot(true)
.withRunAsUser(1000L)
.withAllowPrivilegeEscalation(false)
.withCapabilities(new CapabilitiesBuilder()
.withDrop("ALL")
.build())
.build();
}
private ResourceRequirements createResourceRequirements() {
return new ResourceRequirementsBuilder()
.withLimits(Map.of(
"cpu", new Quantity("500m"),
"memory", new Quantity("512Mi")
))
.withRequests(Map.of(
"cpu", new Quantity("100m"), 
"memory", new Quantity("128Mi")
))
.build();
}
// DTOs for deployment specification
public static class DeploymentSpec {
private String name;
private String namespace = "default";
private String application;
private String component;
private String version;
private int replicas = 1;
private List<ContainerSpec> containers = new ArrayList<>();
// Constructors, getters, setters
}
public static class ContainerSpec {
private String name;
private String image;
private int port = 8080;
private Map<String, String> env = new HashMap<>();
// Constructors, getters, setters
}
}

Gatekeeper Constraint Templates in Java

1. Constraint Template Generator

@Service
public class ConstraintTemplateGenerator {
private final ObjectMapper yamlMapper;
public ConstraintTemplateGenerator() {
this.yamlamlMapper = new ObjectMapper(new YAMLFactory());
this.yamlMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
}
public String generateK8sRequiredResourcesTemplate() {
ConstraintTemplate template = new ConstraintTemplate();
template.setApiVersion("templates.gatekeeper.sh/v1beta1");
template.setKind("ConstraintTemplate");
Metadata metadata = new Metadata();
metadata.setName("k8srequiredresources");
template.setMetadata(metadata);
Spec spec = new Spec();
CRD crd = new CRD();
SpecDefinition specDefinition = new SpecDefinition();
// Define the CRD schema
JSONSchemaProps schema = new JSONSchemaProps();
schema.setType("object");
schema.setProperties(createResourceProperties());
specDefinition.setOpenAPIV3Schema(schema);
crd.setSpec(specDefinition);
spec.setCrd(crd);
// Define the Rego policy
List<Target> targets = new ArrayList<>();
Target target = new Target();
target.setTarget("admission.k8s.gatekeeper.sh");
target.setRego(createRequiredResourcesRego());
targets.add(target);
spec.setTargets(targets);
template.setSpec(spec);
try {
return yamlMapper.writeValueAsString(template);
} catch (JsonProcessingException e) {
throw new ConstraintTemplateException("Failed to generate constraint template", e);
}
}
private String createRequiredResourcesRego() {
return "package k8srequiredresources\n\n" +
"violation[{\"msg\": msg}] {\n" +
"  container := input.review.object.spec.template.spec.containers[_]\n" +
"  not container.resources\n" +
"  msg := sprintf(\"Container %v must have resource limits and requests\", [container.name])\n" +
"}\n\n" +
"violation[{\"msg\": msg}] {\n" +
"  container := input.review.object.spec.template.spec.containers[_]\n" +
"  not container.resources.limits\n" +
"  msg := sprintf(\"Container %v must have resource limits\", [container.name])\n" +
"}\n\n" +
"violation[{\"msg\": msg}] {\n" +
"  container := input.review.object.spec.template.spec.containers[_]\n" +
"  not container.resources.requests\n" +
"  msg := sprintf(\"Container %v must have resource requests\", [container.name])\n" +
"}";
}
private Map<String, JSONSchemaProps> createResourceProperties() {
Map<String, JSONSchemaProps> properties = new HashMap<>();
JSONSchemaProps match = new JSONSchemaProps();
match.setType("object");
match.setProperties(createMatchProperties());
properties.put("match", match);
return properties;
}
private Map<String, JSONSchemaProps> createMatchProperties() {
Map<String, JSONSchemaProps> properties = new HashMap<>();
JSONSchemaProps kinds = new JSONSchemaProps();
kinds.setType("array");
kinds.setItems(new JSONSchemaPropsBuilder().withType("string").build());
properties.put("kinds", kinds);
JSONSchemaProps apiGroups = new JSONSchemaProps();
apiGroups.setType("array");
apiGroups.setItems(new JSONSchemaPropsBuilder().withType("string").build());
properties.put("apiGroups", apiGroups);
return properties;
}
// Inner classes for ConstraintTemplate structure
public static class ConstraintTemplate {
private String apiVersion;
private String kind;
private Metadata metadata;
private Spec spec;
// Getters and setters
}
public static class Metadata {
private String name;
// Getters and setters
}
public static class Spec {
private CRD crd;
private List<Target> targets;
// Getters and setters
}
public static class CRD {
private SpecDefinition spec;
// Getters and setters
}
public static class SpecDefinition {
private JSONSchemaProps openAPIV3Schema;
// Getters and setters
}
public static class Target {
private String target;
private String rego;
// Getters and setters
}
}

CI/CD Integration for Gatekeeper Validation

1. Pre-deployment Validation

@Service
public class CICDValidationService {
private final KubernetesResourceValidator resourceValidator;
private final KubernetesClient kubernetesClient;
public CICDValidationService(KubernetesResourceValidator resourceValidator) {
this.resourceValidator = resourceValidator;
this.kubernetesClient = new DefaultKubernetesClient();
}
public ValidationResult validateKubernetesManifests(File manifestsDir) {
List<ConstraintViolation> allViolations = new ArrayList<>();
try {
List<File> yamlFiles = Files.walk(manifestsDir.toPath())
.filter(path -> path.toString().endsWith(".yaml") || path.toString().endsWith(".yml"))
.map(Path::toFile)
.collect(Collectors.toList());
for (File yamlFile : yamlFiles) {
List<HasMetadata> resources = kubernetesClient.load(
new FileInputStream(yamlFile)).get();
for (HasMetadata resource : resources) {
if (resource instanceof Deployment) {
ValidationResult result = resourceValidator.validateDeployment(
(Deployment) resource);
allViolations.addAll(result.getViolations());
} else if (resource instanceof Service) {
allViolations.addAll(validateService((Service) resource));
} else if (resource instanceof ConfigMap) {
allViolations.addAll(validateConfigMap((ConfigMap) resource));
}
}
}
} catch (Exception e) {
throw new ValidationException("Failed to validate Kubernetes manifests", e);
}
return new ValidationResult(allViolations);
}
public boolean shouldBlockDeployment(ValidationResult result) {
// Block deployment if there are critical violations
return result.getViolations().stream()
.anyMatch(violation -> isCriticalViolation(violation.getConstraint()));
}
private boolean isCriticalViolation(String constraint) {
List<String> criticalConstraints = Arrays.asList(
"K8sNoPrivilegedContainers",
"K8sNoRootContainers", 
"K8sRequiredResources"
);
return criticalConstraints.contains(constraint);
}
public String generateValidationReport(ValidationResult result) {
StringBuilder report = new StringBuilder();
report.append("# Gatekeeper Validation Report\n\n");
if (result.isValid()) {
report.append("✅ All Kubernetes resources comply with Gatekeeper constraints\n");
} else {
report.append("❌ Found ").append(result.getViolations().size())
.append(" constraint violations:\n\n");
result.getViolations().forEach(violation -> 
report.append("- **").append(violation.getConstraint())
.append("**: ").append(violation.getMessage())
.append(" (").append(violation.getResource()).append(")\n"));
}
return report.toString();
}
}

Testing Gatekeeper Constraints

1. Unit Tests for Constraint Validation

@ExtendWith(MockitoExtension.class)
class KubernetesResourceValidatorTest {
private KubernetesResourceValidator validator;
@BeforeEach
void setUp() {
validator = new KubernetesResourceValidator(mock(GatekeeperService.class));
}
@Test
void shouldRejectDeploymentWithoutResourceLimits() {
// Given
Deployment deployment = createTestDeployment();
deployment.getSpec().getTemplate().getSpec().getContainers()
.forEach(container -> container.setResources(null));
// When
ValidationResult result = validator.validateDeployment(deployment);
// Then
assertFalse(result.isValid());
assertTrue(result.getViolations().stream()
.anyMatch(v -> v.getConstraint().equals("K8sRequiredResources")));
}
@Test
void shouldRejectPrivilegedContainer() {
// Given
Deployment deployment = createTestDeployment();
deployment.getSpec().getTemplate().getSpec().getContainers()
.forEach(container -> {
SecurityContext securityContext = new SecurityContext();
securityContext.setPrivileged(true);
container.setSecurityContext(securityContext);
});
// When
ValidationResult result = validator.validateDeployment(deployment);
// Then
assertFalse(result.isValid());
assertTrue(result.getViolations().stream()
.anyMatch(v -> v.getConstraint().equals("K8sNoPrivilegedContainers")));
}
@Test
void shouldAcceptCompliantDeployment() {
// Given
Deployment deployment = createCompliantDeployment();
// When
ValidationResult result = validator.validateDeployment(deployment);
// Then
assertTrue(result.isValid());
assertTrue(result.getViolations().isEmpty());
}
private Deployment createTestDeployment() {
return new DeploymentBuilder()
.withNewMetadata()
.withName("test-app")
.withLabels(Map.of("app", "test"))
.endMetadata()
.withNewSpec()
.withNewTemplate()
.withNewSpec()
.addNewContainer()
.withName("app")
.withImage("nginx:1.19")
.endContainer()
.endSpec()
.endTemplate()
.endSpec()
.build();
}
private Deployment createCompliantDeployment() {
return new DeploymentBuilder()
.withNewMetadata()
.withName("compliant-app")
.withLabels(Map.of(
"app.kubernetes.io/name", "compliant-app",
"app.kubernetes.io/version", "1.0.0"
))
.endMetadata()
.withNewSpec()
.withNewTemplate()
.withNewSpec()
.withSecurityContext(createPodSecurityContext())
.addNewContainer()
.withName("app")
.withImage("nginx:1.19")
.withResources(createResourceRequirements())
.withSecurityContext(createContainerSecurityContext())
.endContainer()
.endSpec()
.endTemplate()
.endSpec()
.build();
}
private ResourceRequirements createResourceRequirements() {
return new ResourceRequirementsBuilder()
.withLimits(Map.of(
"cpu", new Quantity("500m"),
"memory", new Quantity("512Mi")
))
.withRequests(Map.of(
"cpu", new Quantity("100m"),
"memory", new Quantity("128Mi")
))
.build();
}
}

Best Practices for Gatekeeper Constraints

  1. Start Simple - Begin with essential constraints and gradually add complexity
  2. Test Thoroughly - Validate constraints in non-production environments first
  3. Use Meaningful Names - Clear constraint names help with debugging
  4. Document Constraints - Maintain documentation for all constraints
  5. Monitor Audit Results - Regularly review constraint violations
  6. Version Control - Store constraint templates in version control
  7. Gradual Rollout - Use enforcementAction: dryrun initially

Conclusion

Integrating Gatekeeper constraints into Java applications ensures that Kubernetes deployments comply with organizational policies and security standards. By implementing validation logic in Java, you can catch policy violations early in the development cycle, reducing deployment failures and security risks.

Key benefits of this approach:

  • Early Detection - Identify constraint violations during development
  • Consistent Enforcement - Apply the same policies across all environments
  • Developer Empowerment - Provide clear feedback on policy requirements
  • Automated Compliance - Integrate validation into CI/CD pipelines
  • Reduced Operational Overhead - Minimize failed deployments due to policy violations

Whether you're building a internal platform or deploying customer-facing applications, integrating Gatekeeper constraint validation into your Java development workflow ensures that your Kubernetes deployments are secure, compliant, and reliable from the start.


Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper