google-site-verification: google61fe8ba583a51912.html
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