Securing Kubernetes Secrets: A Java Developer’s Guide to Sealed Secrets and Kubeseal

As Java developers deploying to Kubernetes, we face a common dilemma: how to manage sensitive configuration like database passwords, API keys, and certificates in a GitOps workflow. Regular Kubernetes Secrets are only base64-encoded, not encrypted, making them unsafe to commit to source control. Sealed Secrets solves this problem by providing encrypted secrets that can only be decrypted by the target cluster.

Let's explore how Java teams can securely manage secrets using Sealed Secrets and Kubeseal.

What are Sealed Secrets?

Sealed Secrets is a Kubernetes controller and tool that:

  • Encrypts regular Kubernetes Secrets into SealedSecret custom resources
  • Uses cluster-specific keys - secrets encrypted for one cluster cannot be decrypted by another
  • Allows safe storage in Git - SealedSecrets can be committed to version control
  • Decrypts at runtime - the controller in your cluster decrypts SealedSecrets into regular Secrets

Architecture Overview

Java App → Regular Secret → Kubeseal → SealedSecret → Git → Controller → Regular Secret → Pod

Setting Up Sealed Secrets in Your Cluster

First, install the Sealed Secrets controller:

# Install using Helm
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets --namespace kube-system
# Or install using kubectl
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.26.1/controller.yaml

Basic Usage with Java Applications

Step 1: Create a Regular Kubernetes Secret

# secret.yaml - DO NOT COMMIT THIS!
apiVersion: v1
kind: Secret
metadata:
name: my-java-app-secrets
namespace: default
type: Opaque
data:
database-url: amRiYzpxc3FscDovL2RifGhvc3Q6MzMwNi9teWRi
database-username: dXNlcg==
database-password: c2VjcmV0LXBhc3N3b3Jk
api-key: bXktYXBpLWtleQ==
jks-password: bXktanRzLXN0b3JlLXBhc3N3b3Jk

Step 2: Seal the Secret with Kubeseal

# Seal the secret for your cluster
kubeseal -f secret.yaml -o yaml > sealed-secret.yaml
# Or seal from stdin
kubectl create secret generic my-java-app-secrets \
--from-literal=database-password=secret-password \
--from-literal=api-key=my-api-key \
--dry-run=client -o yaml | kubeseal -o yaml > sealed-secret.yaml

The resulting sealed-secret.yaml is safe to commit to Git.

Java Application Integration

Let's create a complete example for a Spring Boot application.

1. Sealed Secret Manifest

# k8s/sealed-secret.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: my-spring-app-secrets
namespace: default
spec:
encryptedData:
database-url: AgA...[encrypted]...Q==
database-username: AgA...[encrypted]...Q==
database-password: AgA...[encrypted]...Q==
api-key: AgA...[encrypted]...Q==
jks-password: AgA...[encrypted]...Q==
keystore-password: AgA...[encrypted]...Q==
template:
metadata:
labels:
app: my-spring-app
type: Opaque

2. Spring Boot Application Configuration

// src/main/java/com/company/config/SecretConfiguration.java
@Configuration
public class SecretConfiguration {
@Value("${DB_URL:jdbc:h2:mem:testdb}")
private String databaseUrl;
@Value("${DB_USERNAME:sa}")
private String databaseUsername;
@Value("${DB_PASSWORD:password}")
private String databasePassword;
@Value("${API_KEY:default-key}")
private String apiKey;
@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(databaseUrl);
dataSource.setUsername(databaseUsername);
dataSource.setPassword(databasePassword);
return dataSource;
}
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add((request, body, execution) -> {
request.getHeaders().add("X-API-Key", apiKey);
return execution.execute(request, body);
});
return restTemplate;
}
}

3. Kubernetes Deployment with Secrets

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-spring-app
spec:
replicas: 2
selector:
matchLabels:
app: my-spring-app
template:
metadata:
labels:
app: my-spring-app
spec:
containers:
- name: app
image: my-registry/my-spring-app:latest
ports:
- containerPort: 8080
env:
- name: DB_URL
valueFrom:
secretKeyRef:
name: my-spring-app-secrets
key: database-url
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: my-spring-app-secrets
key: database-username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-spring-app-secrets
key: database-password
- name: API_KEY
valueFrom:
secretKeyRef:
name: my-spring-app-secrets
key: api-key
- name: SERVER_SSL_KEY_STORE_PASSWORD
valueFrom:
secretKeyRef:
name: my-spring-app-secrets
key: keystore-password
volumeMounts:
- name: keystore-volume
mountPath: /app/keystore
readOnly: true
volumes:
- name: keystore-volume
secret:
secretName: my-spring-app-secrets
items:
- key: keystore-file
path: keystore.p12

Advanced Java Integration Patterns

1. Programmatic Secret Management with CDK8s

// src/main/java/com/company/k8s/SealedSecretGenerator.java
import org.cdk8s.App;
import org.cdk8s.Chart;
import org.cdk8s.ChartProps;
import imports.k8s.*;
import software.constructs.Construct;
public class SealedSecretGenerator extends Chart {
public SealedSecretGenerator(final Construct scope, final String id, 
final SealedSecretProps props) {
super(scope, id);
// Create the regular secret template (for reference)
Secret secret = new Secret(this, "secret-template", SecretProps.builder()
.type("Opaque")
.putStringData("database-url", props.getDatabaseUrl())
.putStringData("database-username", props.getDatabaseUsername())
.putStringData("api-key", props.getApiKey())
.build());
// Note: The actual sealing happens via kubeseal CLI
// This generates the template that you would seal
System.out.println("Generated secret template. Seal with:");
System.out.println("kubeseal -f secret-template.yaml -o yaml > sealed-secret.yaml");
}
public static void main(String[] args) {
App app = new App();
SealedSecretProps props = new SealedSecretProps();
props.setDatabaseUrl("jdbc:postgresql://db-host:5432/myapp");
props.setDatabaseUsername("app-user");
props.setApiKey("super-secret-api-key-123");
new SealedSecretGenerator(app, "sealed-secrets", props);
app.synth();
}
static class SealedSecretProps {
private String databaseUrl;
private String databaseUsername;
private String databasePassword;
private String apiKey;
private String jksPassword;
// Getters and setters...
}
}

2. Java-based Secret Sealing Utility

// src/main/java/com/company/k8s/SecretSealingService.java
package com.company.k8s;
import java.io.*;
import java.nio.file.*;
import java.util.*;
/**
* Utility class to help manage the Sealed Secrets workflow
*/
public class SecretSealingService {
private final String namespace;
private final String kubeConfigPath;
public SecretSealingService(String namespace, String kubeConfigPath) {
this.namespace = namespace;
this.kubeConfigPath = kubeConfigPath;
}
/**
* Creates a Kubernetes Secret manifest and seals it using kubeseal
*/
public void createAndSealSecret(String secretName, Map<String, String> data, 
Path outputDir) throws IOException, InterruptedException {
// 1. Create temporary secret file
Path secretFile = createSecretManifest(secretName, data, outputDir);
// 2. Seal the secret using kubeseal
sealSecret(secretFile, outputDir.resolve(secretName + "-sealed.yaml"));
// 3. Clean up temporary unsealed secret
Files.delete(secretFile);
System.out.println("Sealed secret created: " + 
outputDir.resolve(secretName + "-sealed.yaml"));
}
private Path createSecretManifest(String secretName, Map<String, String> data, 
Path outputDir) throws IOException {
StringBuilder manifest = new StringBuilder();
manifest.append("apiVersion: v1\n");
manifest.append("kind: Secret\n");
manifest.append("metadata:\n");
manifest.append("  name: ").append(secretName).append("\n");
manifest.append("  namespace: ").append(namespace).append("\n");
manifest.append("type: Opaque\n");
manifest.append("data:\n");
for (Map.Entry<String, String> entry : data.entrySet()) {
String base64Value = Base64.getEncoder()
.encodeToString(entry.getValue().getBytes());
manifest.append("  ").append(entry.getKey())
.append(": ").append(base64Value).append("\n");
}
Path secretFile = outputDir.resolve(secretName + "-unsealed.yaml");
Files.write(secretFile, manifest.toString().getBytes());
return secretFile;
}
private void sealSecret(Path inputFile, Path outputFile) 
throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder(
"kubeseal",
"--format", "yaml",
"--scope", "cluster-wide",
"-f", inputFile.toString(),
"-o", outputFile.toString()
);
if (kubeConfigPath != null) {
processBuilder.environment().put("KUBECONFIG", kubeConfigPath);
}
Process process = processBuilder.start();
int exitCode = process.waitFor();
if (exitCode != 0) {
String error = new String(process.getErrorStream().readAllBytes());
throw new RuntimeException("kubeseal failed: " + error);
}
}
/**
* Example usage for a Spring Boot application
*/
public static void main(String[] args) throws Exception {
SecretSealingService service = new SecretSealingService("default", null);
Map<String, String> secrets = new HashMap<>();
secrets.put("database-url", "jdbc:postgresql://prod-db:5432/myapp");
secrets.put("database-username", "prod-user");
secrets.put("database-password", "super-secret-password-123");
secrets.put("api-key", "prod-api-key-456");
secrets.put("jwt-secret", "jwt-signing-secret-789");
service.createAndSealSecret("spring-app-prod-secrets", secrets, 
Paths.get("k8s/secrets/"));
}
}

3. Environment-specific Secret Management

// src/main/java/com/company/k8s/EnvironmentSecretManager.java
@Component
public class EnvironmentSecretManager {
private final String environment;
private final SecretSealingService sealingService;
public EnvironmentSecretManager(@Value("${app.environment:dev}") String environment) {
this.environment = environment;
this.sealingService = new SecretSealingService(environment, null);
}
public void generateEnvironmentSecrets() throws Exception {
Map<String, String> baseSecrets = loadBaseSecrets();
Map<String, String> envSpecificSecrets = loadEnvironmentSpecificSecrets();
Map<String, String> allSecrets = new HashMap<>();
allSecrets.putAll(baseSecrets);
allSecrets.putAll(envSpecificSecrets);
String secretName = String.format("app-secrets-%s", environment);
sealingService.createAndSealSecret(secretName, allSecrets, 
Paths.get("k8s/", environment));
}
private Map<String, String> loadBaseSecrets() {
Map<String, String> secrets = new HashMap<>();
// Common secrets across all environments
secrets.put("log-level", "INFO");
secrets.put("metrics-enabled", "true");
return secrets;
}
private Map<String, String> loadEnvironmentSpecificSecrets() {
Map<String, String> secrets = new HashMap<>();
switch (environment.toLowerCase()) {
case "dev":
secrets.put("database-url", "jdbc:h2:mem:testdb");
secrets.put("database-username", "sa");
secrets.put("database-password", "password");
secrets.put("api-key", "dev-key-123");
break;
case "staging":
secrets.put("database-url", "jdbc:postgresql://staging-db:5432/myapp");
secrets.put("database-username", "staging-user");
secrets.put("database-password", "staging-pass-456");
secrets.put("api-key", "staging-key-789");
break;
case "prod":
secrets.put("database-url", "jdbc:postgresql://prod-db:5432/myapp");
secrets.put("database-username", "prod-user");
secrets.put("database-password", loadProdPasswordFromVault());
secrets.put("api-key", loadProdApiKeyFromVault());
break;
}
return secrets;
}
private String loadProdPasswordFromVault() {
// Integrate with HashiCorp Vault or similar
return "vault-prod-password";
}
private String loadProdApiKeyFromVault() {
// Integrate with HashiCorp Vault or similar
return "vault-prod-api-key";
}
}

CI/CD Integration

GitHub Actions Workflow

# .github/workflows/sealed-secrets.yml
name: Generate Sealed Secrets
on:
push:
branches: [ main ]
workflow_dispatch:
jobs:
generate-secrets:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Install kubeseal
run: |
wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.26.1/kubeseal-0.26.1-linux-amd64.tar.gz
tar -xzf kubeseal-0.26.1-linux-amd64.tar.gz
sudo install -m 755 kubeseal /usr/local/bin/kubeseal
- name: Generate sealed secrets
run: |
mvn compile exec:java -Dexec.mainClass="com.company.k8s.SecretSealingService" -Dexec.args="generate"
- name: Commit sealed secrets
run: |
git config --local user.email "[email protected]"
git config --local user.name "GitHub Action"
git add k8s/secrets/
git commit -m "Update sealed secrets" || exit 0
git push
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Best Practices for Java Teams

  1. Use Different Clusters for Different Environments: Each environment should have its own Sealed Secrets controller with unique keys.
  2. Secret Rotation: Regularly rotate both your application secrets and the Sealed Secrets controller keys.
  3. Access Control: Use RBAC to restrict who can create SealedSecrets and Secrets in your cluster.
  4. Audit Logging: Enable audit logs to track who creates and accesses secrets.
  5. Combine with External Secret Operators: For dynamic secrets, combine with External Secrets Operator.
  6. Secret Template: Use the template field in SealedSecrets to add labels and annotations.
spec:
template:
metadata:
labels:
app: my-java-app
environment: production
annotations:
description: "Database credentials for Spring Boot app"

Troubleshooting Common Issues

// Debug utility for Sealed Secrets
@Component
public class SecretDebugUtil {
@EventListener
public void onApplicationEvent(ApplicationReadyEvent event) {
debugSecrets();
}
private void debugSecrets() {
String[] secretVars = {
"DB_URL", "DB_USERNAME", "DB_PASSWORD", "API_KEY"
};
for (String var : secretVars) {
String value = System.getenv(var);
if (value == null || value.isEmpty()) {
log.warn("Secret environment variable {} is not set", var);
} else if (value.startsWith("Ag")) {
log.error("Secret {} appears to be still encrypted!", var);
} else {
log.info("Secret {} is properly set", var);
}
}
}
}

Conclusion

Sealed Secrets with Kubeseal provides a robust solution for managing Kubernetes secrets in GitOps workflows for Java applications. By following this guide, Java teams can:

  • Safely store encrypted secrets in version control
  • Automate secret management in CI/CD pipelines
  • Maintain cluster-specific security with separate encryption keys
  • Integrate seamlessly with Spring Boot and other Java frameworks
  • Implement environment-specific secret strategies

The combination of Sealed Secrets for static secret encryption and tools like External Secrets Operator for dynamic secret management creates a comprehensive secret management strategy that scales from development to production.

Start by setting up Sealed Secrets in your development cluster and gradually roll out to production environments. Your Java applications will benefit from improved security without sacrificing the automation benefits of GitOps.

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.

Leave a Reply

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


Macro Nepal Helper