Foundation · Configure

Secrets Management & Environment Safety

Passwords, API keys, and tokens come from the environment or a vault—never from files under version control.

Primary source: Spring Boot 3.5 Reference: Externalized Configuration

20 min

Lesson 4 of 4

1d streak

The Architectural Question

A credential pushed to a public repository is compromised forever; scanners harvest it within minutes, and deleting the file does not remove it from git history.

What this guide proves

  • Reference secrets with ${ENV_VAR} placeholders instead of literals
  • Provide non-secret defaults while keeping secrets required
  • Scan history and rotate anything that ever leaked

Secrets Management & The Twelve-Factor Methodology

Credentials, private API keys, TLS certificates, and database passwords must never be stored in Git repositories or hardcoded into application properties files. The Twelve-Factor App specification strictly mandates that configuration that varies between deploys (and all secrets) must be injected strictly from the deployment execution environment.

  1. 011. Never commit plain-text credentials to version control (even in private repos)
  2. 022. Inject secrets into runtime memory via container environment variables or volume mounts
  3. 033. Mask and sanitize secrets in logs, metrics, exception traces, and actuator endpoints
  4. 044. Implement automated secret rotation without requiring application restarts
  5. 055. Restrict access using least-privilege IAM roles and secret vault engines

The Danger of Git Purging

Once a secret is committed to a Git repository, treat it as compromised immediately. Simply deleting the file in a subsequent commit does NOT remove it from Git history or commit snapshots. The secret must be revoked and rotated immediately.

Active Recall Checkpoint

Why is setting ${DB_PASSWORD:} (with an empty colon fallback) dangerous for production database credentials?

Hint: What happens if the operator forgets to set the DB_PASSWORD environment variable?

Show answer & explanation

The colon fallback ${DB_PASSWORD:} supplies an empty string "" as default. Instead of failing fast at startup with a clear error, the app boots and attempts to connect with an empty password, leading to confusing authentication errors or security bypasses.

Enterprise Secrets: Kubernetes ConfigTree, Vault & Cloud Providers

Modern cloud-native Spring Boot applications leverage specialized secret engines rather than static environment variables. Spring Boot provides native integration via spring.config.import.

Secrets MechanismSpring Boot IntegrationOperational Advantage
Kubernetes Secrets (Mounted Volumes)spring.config.import=configtree:/etc/secrets/Each file in the directory becomes a property key. Kubernetes manages rotation; no cloud SDK required.
HashiCorp VaultSpring Cloud Vault (vault://)Dynamic credential generation, leased access, and centralized enterprise audit logging.
AWS Secrets Manager / Parameter StoreSpring Cloud AWS Secrets ManagerDirect IAM-authenticated retrieval; secrets never touch disk or container environment lists.
Encrypted Properties (Jasypt / Age)Jasypt Spring Boot Starter (ENC(...))Enables storing encrypted ciphertext in Git; private decryption key is supplied only at boot time.
application.yml
# Reading Kubernetes secret volume mount as a config tree:
spring:
  config:
    import:
      - "configtree:/etc/secrets/database/"
      - "configtree:/etc/secrets/stripe/"
  datasource:
    # Files /etc/secrets/database/username and /etc/secrets/database/password are mapped automatically:
    username: "${username}"
    password: "${password}"

Active Recall Checkpoint

What is the primary security advantage of Kubernetes configtree: volume mounts over container environment variables (env)?

Hint: Can environment variables be read by other processes or crash reports?

Show answer & explanation

Container environment variables can be inspected via /proc/[pid]/environ, Docker inspect, or crash logs. Volume-mounted files in tmpfs exist purely in RAM and are only accessible by the specific process reading that file path.

Actuator Sanitization & Preventing Secret Leakage

Spring Boot Actuator endpoints such as /actuator/env and /actuator/configprops inspect the live Environment. Without explicit configuration, these endpoints could expose database credentials, OAuth client secrets, and internal tokens to unauthorized network callers.

application.yml
management:
  endpoint:
    env:
      show-values: when_authorized
      roles: "SYSTEM_ADMIN"
    configprops:
      show-values: when_authorized
      roles: "SYSTEM_ADMIN"
  endpoints:
    web:
      exposure:
        include: "health,info,metrics,prometheus" # Exclude env and configprops from public exposure!

By default, Spring Boot 3 sanitizes keys containing password, secret, key, token, credential, vcap_services, sun_java_command, replacing their values with ******. You can configure custom sanitization regexes via management.endpoint.env.keys-to-sanitize.

Never Expose /actuator/env Unauthenticated

Exposing /actuator/env publicly is a high-severity security vulnerability (OWASP A05: Security Misconfiguration). Attackers can inspect runtime configuration or exploit property injection endpoints to achieve Remote Code Execution (RCE).

Active Recall Checkpoint

What setting ensures that secrets in /actuator/env are masked with asterisks unless accessed by a user with SYSTEM_ADMIN authority?

Hint: Look at the management.endpoint.env.show-values options in Spring Boot.

Show answer & explanation

Set management.endpoint.env.show-values=when_authorized along with configuring the authorized roles via management.endpoint.env.roles=SYSTEM_ADMIN.

Deliberate Failure Lab: Missing Secret Startup Failure

To ensure fail-fast guarantees, never provide silent default fallbacks for security credentials. Reference the environment variable directly without a default: ${DB_PASSWORD}. If the environment variable is not defined, Spring Boot refuses to start.

missing-secret.log
***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to bind properties under 'spring.datasource.password' to java.lang.String:

    Property: spring.datasource.password
    Value: "${DB_PASSWORD}"
    Origin: "spring.datasource.password" from property source "application.yml"
    Reason: Could not resolve placeholder 'DB_PASSWORD' in value "${DB_PASSWORD}"

Action:

Set the environment variable DB_PASSWORD before starting the application.

The Principle of Safe Defaults

For non-sensitive properties (like timeout=5s), provide sensible defaults. For security credentials (API keys, passwords, signing secrets), NEVER provide a default. Force startup to fail loudly so configuration mistakes are caught immediately during deployment.

Active Recall Checkpoint

How do you configure an optional environment variable with a fallback vs a strictly required one in application.yml?

Hint: Compare the syntax ${KEY} with ${KEY:default}.

Show answer & explanation

${REQUIRED_SECRET} has no colon and fails startup if unset. ${OPTIONAL_VAR:defaultValue} uses everything after the colon as the fallback value if the property is missing.

Runtime Architecture & Flow

Resolution model

Configuration resolution checklist

  1. 1

    Reference secrets with ${ENV_VAR} placeholders instead of literals

  2. 2

    Provide non-secret defaults while keeping secrets required

  3. 3

    Scan history and rotate anything that ever leaked

Inspect the effective application state before assuming which declaration won.

Working Implementation

Code Walkthrough

Replace a committed database password with DB_PASSWORD, run without setting it to see the failure, then supply it via environment.

SecretsSafetyLab.java
package com.example.config;

import jakarta.validation.constraints.NotBlank;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;

@Configuration
public class SecretsSafetyLab {
    private static final Logger log = LoggerFactory.getLogger(SecretsSafetyLab.class);

    @ConfigurationProperties(prefix = "app.database")
    @Validated
    public record DatabaseCredentials(
            @NotBlank String username,
            @NotBlank String password
    ) {}

    @Bean
    CommandLineRunner verifySecretBinding(DatabaseCredentials creds) {
        return args -> {
            // Sensitive values are validated at boot without logging plain text
            log.info("Database credentials bound successfully for user: {}", creds.username());
            log.info("Password length: {} characters (masked in telemetry)", creds.password().length());
        };
    }
}
Always use ${DB_PASSWORD} without a fallback colon to fail fast at startup if the secret is missing.
@Validated with @NotBlank prevents silent empty-string credentials from passing configuration binding.
Secrets must never be printed to stdout or logs; inspect length or hash for diagnostic verification only.

Synthesis & Production Verification

Production Acceptance Criteria

  • Resolve one placeholder from an environment variable successfully
  • Fail startup loudly when a required secret is absent
  • Explain where your platform stores injected secrets

Architectural & Technical Review

Core Interview Questions

Q01What is the Twelve-Factor App principle regarding secrets management?Show answer

Strict separation of config from code: credentials and secrets must be injected from the deployment environment (environment variables or secret volume mounts) and never stored in version control.

Q02How does Kubernetes configtree integration work with Spring Boot?Show answer

Spring Boot imports mounted secret directories via spring.config.import=configtree:/etc/secrets/, mapping each file in the volume directly to a property key in memory without exposing values in container environments.

Q03How does Spring Boot Actuator sanitize sensitive keys on /actuator/env?Show answer

Keys matching configured patterns (password, secret, key, token) are masked as ****** by default, and can be restricted to authorized roles via management.endpoint.env.show-values=when_authorized.

Production Pitfalls

Critical Anti-Patterns

  • Pitfall: Using empty default fallbacks for required passwords — Using ${DB_PASSWORD:} supplies an empty string rather than failing fast at startup when the environment variable is missing.
  • Pitfall: Exposing /actuator/env unauthenticated on public networks — Public exposure of environment endpoints leaks credentials and configurations, risking remote exploitation.

Module Certification Quiz

Prove Module Mastery

Comprehensive question set covering all lessons in this module.

Take Module Quiz

Primary Documentation & Specifications

Supplementary Module Resources