As Java applications increasingly run in Kubernetes environments, ensuring container security at the pod level becomes paramount. Pod Security Admission (PSA) is a Kubernetes feature that enforces security standards by validating and mutating pods against predefined security policies. For Java development teams, understanding and implementing PSA ensures that containerized JVM applications run with appropriate security contexts and minimal attack surfaces.
What is Pod Security Admission?
Pod Security Admission is a built-in Kubernetes admission controller that evaluates pods against the Pod Security Standards (PSS). It provides three modes of enforcement: Enforce (rejects non-compliant pods), Audit (logs violations), and Warn (warns users). For Java applications, PSA ensures containers run with secure defaults, proper privilege restrictions, and appropriate security contexts.
Why PSA is Essential for Java Applications in Kubernetes
- Privilege Escalation Prevention: Java applications should run as non-root users to limit potential damage from exploits.
- Resource Access Control: Restrict container capabilities, filesystem access, and host resources.
- Consistent Security Posture: Enforce uniform security standards across all Java microservices.
- Compliance Requirements: Meet regulatory standards (SOC2, PCI-DSS, HIPAA) for container security.
- Zero-Day Exploit Mitigation: Reduce attack surface for vulnerabilities like Log4Shell.
Pod Security Standards for Java Applications
PSA defines three security levels for pods:
1. Privileged (Unrestricted)
- No restrictions (not recommended for production)
2. Baseline (Minimal Restrictions)
- Prevents known privilege escalations
- Allows common container features
3. Restricted (Highly Restrictive)
- Follows current security best practices
- Maximally restrictive for production workloads
Configuring PSA for Java Applications
1. Namespace-Level PSA Configuration
# namespace-psa.yaml apiVersion: v1 kind: Namespace metadata: name: java-apps-production labels: pod-security.kubernetes.io/enforce: restricted pod-security.kubernetes.io/enforce-version: latest pod-security.kubernetes.io/audit: restricted pod-security.kubernetes.io/audit-version: latest pod-security.kubernetes.io/warn: restricted pod-security.kubernetes.io/warn-version: latest
2. Cluster-Wide PSA Configuration
# psa-config.yaml apiVersion: apiserver.config.k8s.io/v1 kind: AdmissionConfiguration plugins: - name: PodSecurity configuration: apiVersion: pod-security.admission.config.k8s.io/v1 kind: PodSecurityConfiguration defaults: enforce: "restricted" enforce-version: "latest" audit: "restricted" audit-version: "latest" warn: "restricted" warn-version: "latest" exemptions: # Exclude system namespaces usernames: [] runtimeClassNames: [] namespaces: - kube-system - kube-public - kube-node-lease
Java Application Deployment with PSA Compliance
1. Secure Java Pod Specification
# deployment-secure-java.yaml apiVersion: apps/v1 kind: Deployment metadata: name: java-order-service namespace: java-apps-production labels: app: order-service version: v1.2.0 spec: replicas: 3 selector: matchLabels: app: order-service template: metadata: labels: app: order-service version: v1.2.0 spec: # Pod Security Context - Applies to all containers securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 fsGroup: 1000 seccompProfile: type: RuntimeDefault serviceAccountName: java-app-sa automountServiceAccountToken: false containers: - name: java-app image: company/java-order-service:1.2.0 imagePullPolicy: IfNotPresent # Container Security Context securityContext: allowPrivilegeEscalation: false privileged: false readOnlyRootFilesystem: true capabilities: drop: - ALL add: - NET_BIND_SERVICE # Only if app binds to privileged ports (<1024) seccompProfile: type: RuntimeDefault ports: - containerPort: 8080 name: http protocol: TCP - containerPort: 9090 name: metrics protocol: TCP env: - name: JAVA_OPTS value: > -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/./urandom -Djava.net.preferIPv4Stack=true - name: SPRING_PROFILES_ACTIVE value: "production" resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "768Mi" cpu: "500m" livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 scheme: HTTP initialDelaySeconds: 60 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 scheme: HTTP initialDelaySeconds: 30 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 1 # Mount volumes for writable directories volumeMounts: - name: tmp mountPath: /tmp readOnly: false - name: logs mountPath: /app/logs readOnly: false - name: config mountPath: /app/config readOnly: true volumes: - name: tmp emptyDir: medium: Memory sizeLimit: 100Mi - name: logs emptyDir: sizeLimit: 1Gi - name: config configMap: name: java-app-config
2. Java-Specific PSA Considerations
# Java application with special requirements apiVersion: apps/v1 kind: Deployment metadata: name: java-debug-service namespace: java-apps-staging labels: pod-security.kubernetes.io/enforce: baseline # More lenient for debugging spec: template: spec: securityContext: runAsNonRoot: true runAsUser: 1000 containers: - name: java-app image: company/java-app:debug securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL # Allow ptrace for debugging capabilities: add: - SYS_PTRACE env: - name: JAVA_TOOL_OPTIONS value: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" ports: - containerPort: 5005 name: debug protocol: TCP
Java Application Security Context Implementation
1. Dynamic Security Context Adjustment
@Component
public class PodSecurityValidator {
private final KubernetesClient kubernetesClient;
public PodSecurityValidator(KubernetesClient kubernetesClient) {
this.kubernetesClient = kubernetesClient;
}
public void validateDeploymentSecurity(Deployment deployment) {
PodSpec podSpec = deployment.getSpec().getTemplate().getSpec();
// Check PSA compliance
List<String> violations = new ArrayList<>();
// Check runAsNonRoot
if (podSpec.getSecurityContext() == null ||
!Boolean.TRUE.equals(podSpec.getSecurityContext().getRunAsNonRoot())) {
violations.add("Must set runAsNonRoot=true in pod security context");
}
// Check container security contexts
for (Container container : podSpec.getContainers()) {
if (container.getSecurityContext() == null) {
violations.add("Container " + container.getName() + " missing security context");
} else {
SecurityContext ctx = container.getSecurityContext();
// Check privilege escalation
if (ctx.getAllowPrivilegeEscalation() != null &&
ctx.getAllowPrivilegeEscalation()) {
violations.add("Container " + container.getName() + " allows privilege escalation");
}
// Check read-only root filesystem
if (ctx.getReadOnlyRootFilesystem() == null ||
!ctx.getReadOnlyRootFilesystem()) {
violations.add("Container " + container.getName() + " root filesystem not read-only");
}
}
}
if (!violations.isEmpty()) {
throw new SecurityException("PSA violations detected: " + String.join(", ", violations));
}
}
}
2. Java Application Runtime Security Checks
@SpringBootApplication
public class SecureJavaApplication {
public static void main(String[] args) {
// Verify runtime security context before starting
verifyRuntimeSecurity();
SpringApplication.run(SecureJavaApplication.class, args);
}
private static void verifyRuntimeSecurity() {
// Check if running as root (should be false)
String user = System.getProperty("user.name");
if ("root".equals(user)) {
System.err.println("ERROR: Application should not run as root");
System.exit(1);
}
// Check for dangerous system properties
List<String> dangerousProps = Arrays.asList(
"java.security.manager",
"java.rmi.server.hostname"
);
for (String prop : dangerousProps) {
String value = System.getProperty(prop);
if (value != null && !value.isEmpty()) {
System.err.println("WARNING: Potentially dangerous property set: " + prop);
}
}
// Verify file system permissions
verifyFilePermissions();
}
private static void verifyFilePermissions() {
Path tmpDir = Paths.get("/tmp");
if (Files.isWritable(tmpDir)) {
System.out.println("INFO: /tmp directory is writable (required for Java temp files)");
}
// Ensure sensitive directories are not world-writable
List<Path> sensitivePaths = Arrays.asList(
Paths.get("/etc/passwd"),
Paths.get("/etc/shadow"),
Paths.get("/proc")
);
for (Path path : sensitivePaths) {
if (Files.exists(path) && Files.isReadable(path)) {
System.err.println("WARNING: Sensitive path readable: " + path);
}
}
}
}
PSA Enforcement in CI/CD Pipeline
1. Pre-Deployment Validation Script
@Component
public class PSAValidationService {
public ValidationResult validateDeploymentYaml(String yamlContent) {
List<ValidationRule> rules = Arrays.asList(
new RunAsNonRootRule(),
new ReadOnlyRootFilesystemRule(),
new PrivilegeEscalationRule(),
new SeccompProfileRule(),
new CapabilitiesRule()
);
ValidationResult result = new ValidationResult();
for (ValidationRule rule : rules) {
try {
rule.validate(yamlContent);
} catch (ValidationException e) {
result.addViolation(e.getMessage());
}
}
return result;
}
// Example rule implementation
private static class RunAsNonRootRule implements ValidationRule {
@Override
public void validate(String yaml) throws ValidationException {
if (!yaml.contains("runAsNonRoot: true")) {
throw new ValidationException("Pod must set runAsNonRoot: true");
}
}
}
}
2. GitOps Validation with Kyverno
# kyverno-psa-policy.yaml apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: require-psa-for-java-apps spec: validationFailureAction: enforce background: true rules: - name: check-pod-security-context match: resources: kinds: - Pod - Deployment - StatefulSet - DaemonSet - Job - CronJob validate: message: "Java applications must run with restricted PSA profile" pattern: spec: securityContext: runAsNonRoot: true seccompProfile: type: "RuntimeDefault" =(ephemeralContainers): - securityContext: runAsNonRoot: true containers: - securityContext: =(allowPrivilegeEscalation): false privileged: false readOnlyRootFilesystem: true capabilities: drop: - "ALL"
Handling Java-Specific PSA Exceptions
1. JVM Debugging and Profiling
# Debug deployment with PSA exemptions apiVersion: v1 kind: Pod metadata: name: java-debug-pod namespace: java-apps-debug annotations: # Request PSA exemption for debugging pod-security.kubernetes.io/exempt: "debugging" spec: securityContext: runAsNonRoot: true runAsUser: 1000 containers: - name: java-app image: company/java-app:debug securityContext: allowPrivilegeEscalation: false capabilities: add: - SYS_PTRACE # Required for Java debugging drop: - ALL env: - name: JAVA_TOOL_OPTIONS value: > -agentlib:jdwp=transport=dt_socket, server=y,suspend=n, address=*:5005 -XX:+FlightRecorder -XX:StartFlightRecording=duration=60s, filename=/tmp/recording.jfr
2. Java Performance Monitoring
# Pod with performance monitoring capabilities apiVersion: apps/v1 kind: Deployment metadata: name: java-perf-monitored spec: template: spec: securityContext: runAsNonRoot: true runAsUser: 1000 containers: - name: java-app image: company/java-app:perf securityContext: allowPrivilegeEscalation: false capabilities: add: - SYS_PTRACE # For async-profiler drop: - ALL volumeMounts: - name: perf-tools mountPath: /tmp/perf readOnly: true volumes: - name: perf-tools hostPath: path: /usr/lib/linux-tools type: Directory
Monitoring PSA Compliance for Java Applications
1. Compliance Reporting
@Service
public class PSAComplianceMonitor {
private final KubernetesClient kubernetesClient;
private final MeterRegistry meterRegistry;
public PSAComplianceMonitor(KubernetesClient kubernetesClient,
MeterRegistry meterRegistry) {
this.kubernetesClient = kubernetesClient;
this.meterRegistry = meterRegistry;
}
@Scheduled(fixedRate = 300000) // Every 5 minutes
public void monitorPSACompliance() {
List<Pod> pods = kubernetesClient.pods().inAnyNamespace().list().getItems();
int compliantPods = 0;
int nonCompliantPods = 0;
for (Pod pod : pods) {
if (isPSACompliant(pod)) {
compliantPods++;
} else {
nonCompliantPods++;
logNonCompliantPod(pod);
}
}
// Record metrics
meterRegistry.gauge("psa.compliant.pods", compliantPods);
meterRegistry.gauge("psa.noncompliant.pods", nonCompliantPods);
if (nonCompliantPods > 0) {
logger.warn("Found {} non-compliant pods", nonCompliantPods);
}
}
private boolean isPSACompliant(Pod pod) {
PodSpec spec = pod.getSpec();
// Check pod-level security context
if (spec.getSecurityContext() == null) {
return false;
}
// Check container security contexts
return spec.getContainers().stream()
.allMatch(this::isContainerCompliant);
}
}
Best Practices for Java Applications with PSA
- Always Run as Non-Root: Java applications don't need root privileges.
- Use Read-Only Root Filesystem: Mount writable volumes only where necessary.
- Drop All Capabilities: Add back only specific capabilities if absolutely required.
- Use RuntimeDefault Seccomp Profile: Provides good security with compatibility.
- Set Resource Limits: Prevent resource exhaustion attacks.
- Automount Service Account Tokens: Disable unless explicitly needed.
- Use Pod Security Standards: Adhere to Restricted profile for production.
Common PSA Issues and Solutions for Java Apps
Problem: Java application needs to write to filesystem.
# Solution: Use emptyDir volumes for writable areas volumeMounts: - name: tmp mountPath: /tmp - name: logs mountPath: /app/logs volumes: - name: tmp emptyDir: medium: Memory sizeLimit: 100Mi - name: logs emptyDir: sizeLimit: 1Gi
Problem: Java debugger/profiler requires additional capabilities.
# Solution: Use baseline profile for debugging namespaces metadata: namespace: java-apps-debug labels: pod-security.kubernetes.io/enforce: baseline pod-security.kubernetes.io/enforce-version: latest
Conclusion
Pod Security Admission provides a critical layer of security for Java applications running in Kubernetes. By enforcing security standards at the pod level, PSA ensures that Java containers run with minimal privileges, reduced attack surfaces, and consistent security postures across all environments.
For Java development teams, implementing PSA requires careful consideration of JVM-specific requirements while adhering to security best practices. The balance between security and functionality can be achieved through proper pod security contexts, appropriate namespace labeling, and judicious use of exemptions for legitimate requirements like debugging and profiling.
By embracing Pod Security Admission, Java teams can significantly enhance the security of their containerized applications while maintaining operational efficiency and developer productivity in Kubernetes environments.
Advanced Java Supply Chain Security, Kubernetes Hardening & Runtime Threat Detection
Sigstore Rekor in Java – https://macronepal.com/blog/sigstore-rekor-in-java/
Explains integrating Sigstore Rekor into Java systems to create a transparent, tamper-proof log of software signatures and metadata for verifying supply chain integrity.
Securing Java Applications with Chainguard Wolfi – https://macronepal.com/blog/securing-java-applications-with-chainguard-wolfi-a-comprehensive-guide/
Explains using Chainguard Wolfi minimal container images to reduce vulnerabilities and secure Java applications with hardened, lightweight runtime environments.
Cosign Image Signing in Java Complete Guide – https://macronepal.com/blog/cosign-image-signing-in-java-complete-guide/
Explains how to digitally sign container images using Cosign in Java-based workflows to ensure authenticity and prevent unauthorized modifications.
Secure Supply Chain Enforcement Kyverno Image Verification for Java Containers – https://macronepal.com/blog/secure-supply-chain-enforcement-kyverno-image-verification-for-java-containers/
Explains enforcing Kubernetes policies with Kyverno to verify container image signatures and ensure only trusted Java container images are deployed.
Pod Security Admission in Java Securing Kubernetes Deployments for JVM Applications – https://macronepal.com/blog/pod-security-admission-in-java-securing-kubernetes-deployments-for-jvm-applications/
Explains Kubernetes Pod Security Admission policies that enforce security rules like restricted privileges and safe configurations for Java workloads.
Securing Java Applications at Runtime Kubernetes Security Context – https://macronepal.com/blog/securing-java-applications-at-runtime-a-guide-to-kubernetes-security-context/
Explains how Kubernetes security contexts control runtime permissions, user IDs, and access rights for Java containers to improve isolation.
Process Anomaly Detection in Java Behavioral Monitoring – https://macronepal.com/blog/process-anomaly-detection-in-java-comprehensive-behavioral-monitoring-2/
Explains detecting abnormal runtime behavior in Java applications to identify potential security threats using process monitoring techniques.
Achieving Security Excellence CIS Benchmark Compliance for Java Applications – https://macronepal.com/blog/achieving-security-excellence-implementing-cis-benchmark-compliance-for-java-applications/
Explains applying CIS security benchmarks to Java environments to standardize hardening and improve overall system security posture.
Process Anomaly Detection in Java Behavioral Monitoring – https://macronepal.com/blog/process-anomaly-detection-in-java-comprehensive-behavioral-monitoring/
Explains behavioral monitoring of Java processes to detect anomalies and improve runtime security through continuous observation and analysis.