Pod Security Admission in Java: Securing Kubernetes Deployments for JVM Applications

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

  1. Privilege Escalation Prevention: Java applications should run as non-root users to limit potential damage from exploits.
  2. Resource Access Control: Restrict container capabilities, filesystem access, and host resources.
  3. Consistent Security Posture: Enforce uniform security standards across all Java microservices.
  4. Compliance Requirements: Meet regulatory standards (SOC2, PCI-DSS, HIPAA) for container security.
  5. 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

  1. Always Run as Non-Root: Java applications don't need root privileges.
  2. Use Read-Only Root Filesystem: Mount writable volumes only where necessary.
  3. Drop All Capabilities: Add back only specific capabilities if absolutely required.
  4. Use RuntimeDefault Seccomp Profile: Provides good security with compatibility.
  5. Set Resource Limits: Prevent resource exhaustion attacks.
  6. Automount Service Account Tokens: Disable unless explicitly needed.
  7. 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.


Leave a Reply

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


Macro Nepal Helper