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
- Use Different Clusters for Different Environments: Each environment should have its own Sealed Secrets controller with unique keys.
- Secret Rotation: Regularly rotate both your application secrets and the Sealed Secrets controller keys.
- Access Control: Use RBAC to restrict who can create SealedSecrets and Secrets in your cluster.
- Audit Logging: Enable audit logs to track who creates and accesses secrets.
- Combine with External Secret Operators: For dynamic secrets, combine with External Secrets Operator.
- 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.