Secret Management - Secure Configuration Management
Secure management of secrets is critical in modern applications. This chapter covers HashiCorp Vault integration, encrypted properties, and cloud-based secret management solutions for Spring Boot applications.
HashiCorp Vault Integration (Spring Boot)
Spring Cloud Vault Configuration
Spring Cloud Vault enables applications to automatically retrieve secrets from Vault:
yaml
spring:
cloud:
vault:
host: vault.example.com
port: 8200
scheme: https
authentication: TOKEN
token: ${VAULT_TOKEN}
kv:
enabled: true
backend: secret
application-name: myapp
generic:
enabled: true
backend: secret
default-context: myapp
application-name: myapp
Automatic Secret Injection
java
@Configuration
@EnableConfigurationProperties
public class VaultConfiguration {
@Value("${database.password}")
private String databasePassword;
@Value("${api.secret}")
private String apiSecret;
// Values automatically retrieved from Vault
}
Dynamic Configuration Refresh
java
@Component
@RefreshScope
public class DynamicConfiguration {
@Value("${dynamic.secret}")
private String dynamicSecret;
public String getSecret() {
return dynamicSecret;
}
}
Vault Authentication Methods
AppRole Authentication
java
@Configuration
public class VaultConfig {
@Bean
public VaultTemplate vaultTemplate() {
VaultEndpoint vaultEndpoint = new VaultEndpoint();
vaultEndpoint.setHost("vault.example.com");
vaultEndpoint.setPort(8200);
vaultEndpoint.setScheme("https");
AppRoleAuthentication authentication = new AppRoleAuthentication(
AppRoleAuthenticationOptions.builder()
.roleId(RoleId.provided("my-role-id"))
.secretId(SecretId.provided("my-secret-id"))
.build()
);
return new VaultTemplate(vaultEndpoint, authentication);
}
}
Kubernetes Authentication
java
@Configuration
public class KubernetesVaultConfig {
@Bean
public VaultTemplate vaultTemplate() {
VaultEndpoint vaultEndpoint = VaultEndpoint.create(
"vault.example.com", 8200);
KubernetesAuthentication authentication =
new KubernetesAuthentication(
KubernetesAuthenticationOptions.builder()
.role("my-k8s-role")
.jwtSupplier(() -> {
try {
return Files.readString(
Paths.get("/var/run/secrets/kubernetes.io/serviceaccount/token"));
} catch (IOException e) {
throw new VaultException("Unable to read JWT token", e);
}
})
.build()
);
return new VaultTemplate(vaultEndpoint, authentication);
}
}
AWS IAM Authentication
java
@Configuration
public class AwsIamVaultConfig {
@Bean
public VaultTemplate vaultTemplate() {
VaultEndpoint vaultEndpoint = VaultEndpoint.create(
"vault.example.com", 8200);
AwsIamAuthentication authentication = new AwsIamAuthentication(
AwsIamAuthenticationOptions.builder()
.role("my-aws-role")
.credentialsProvider(DefaultCredentialsProvider.create())
.build()
);
return new VaultTemplate(vaultEndpoint, authentication);
}
}
Spring Boot Configuration Security
Encrypted Properties with Jasypt
xml
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>
yaml
jasypt:
encryptor:
password: ${JASYPT_PASSWORD}
algorithm: PBEWITHHMACSHA512ANDAES_256
key-obtention-iterations: 1000
pool-size: 1
provider-name: SunJCE
salt-generator-classname: org.jasypt.salt.RandomSaltGenerator
iv-generator-classname: org.jasypt.iv.RandomIvGenerator
string-output-type: base64
# Encrypted values
database:
password: ENC(encrypted_password_here)
api:
secret: ENC(encrypted_api_secret_here)
Configuration Encryption Utility
java
@Component
public class ConfigurationEncryptor {
private final StringEncryptor encryptor;
public ConfigurationEncryptor(StringEncryptor encryptor) {
this.encryptor = encryptor;
}
public String encrypt(String plainText) {
return encryptor.encrypt(plainText);
}
public String decrypt(String encryptedText) {
return encryptor.decrypt(encryptedText);
}
}
Environment Variable-Based Encryption
java
@Configuration
public class EncryptionConfig {
@Bean("jasyptStringEncryptor")
public StringEncryptor stringEncryptor() {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(getEncryptionPassword());
config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256");
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);
return encryptor;
}
private String getEncryptionPassword() {
String password = System.getenv("JASYPT_ENCRYPTOR_PASSWORD");
if (password == null) {
throw new IllegalStateException("JASYPT_ENCRYPTOR_PASSWORD environment variable not defined");
}
return password;
}
}
Database Security Patterns
Connection Security
yaml
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb?ssl=true&sslmode=verify-full
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
hikari:
connection-test-query: SELECT 1
validation-timeout: 3000
leak-detection-threshold: 60000
maximum-pool-size: 20
minimum-idle: 5
properties:
ssl: true
sslmode: verify-full
sslcert: ${SSL_CERT_PATH}
sslkey: ${SSL_KEY_PATH}
sslrootcert: ${SSL_ROOT_CERT_PATH}
Database Encryption
java
@Entity
@Table(name = "secure_data")
public class SecureData {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Convert(converter = EncryptedStringConverter.class)
@Column(name = "sensitive_data")
private String sensitiveData;
@Convert(converter = EncryptedStringConverter.class)
@Column(name = "personal_info")
private String personalInfo;
// getters and setters
}
JPA Attribute Converter
java
@Converter
public class EncryptedStringConverter implements AttributeConverter<String, String> {
private final AESUtil aesUtil;
public EncryptedStringConverter() {
this.aesUtil = new AESUtil(getEncryptionKey());
}
@Override
public String convertToDatabaseColumn(String attribute) {
if (attribute == null) {
return null;
}
try {
return aesUtil.encrypt(attribute);
} catch (Exception e) {
throw new RuntimeException("Encryption error", e);
}
}
@Override
public String convertToEntityAttribute(String dbData) {
if (dbData == null) {
return null;
}
try {
return aesUtil.decrypt(dbData);
} catch (Exception e) {
throw new RuntimeException("Decryption error", e);
}
}
private String getEncryptionKey() {
return System.getenv("DB_ENCRYPTION_KEY");
}
}
Key Management Best Practices
Encryption at Rest
java
@Service
public class FileEncryptionService {
private final AESUtil aesUtil;
public FileEncryptionService() {
this.aesUtil = new AESUtil(getFileEncryptionKey());
}
public void encryptFile(String inputPath, String outputPath) throws Exception {
byte[] fileContent = Files.readAllBytes(Paths.get(inputPath));
byte[] encryptedContent = aesUtil.encrypt(fileContent);
Files.write(Paths.get(outputPath), encryptedContent);
}
public void decryptFile(String inputPath, String outputPath) throws Exception {
byte[] encryptedContent = Files.readAllBytes(Paths.get(inputPath));
byte[] decryptedContent = aesUtil.decrypt(encryptedContent);
Files.write(Paths.get(outputPath), decryptedContent);
}
private String getFileEncryptionKey() {
return System.getenv("FILE_ENCRYPTION_KEY");
}
}
Backup Encryption
java
@Service
public class BackupEncryptionService {
private final StringEncryptor encryptor;
public BackupEncryptionService(StringEncryptor encryptor) {
this.encryptor = encryptor;
}
public void createEncryptedBackup(String data, String backupPath) throws IOException {
String encryptedData = encryptor.encrypt(data);
Files.write(Paths.get(backupPath), encryptedData.getBytes());
}
public String restoreFromEncryptedBackup(String backupPath) throws IOException {
String encryptedData = Files.readString(Paths.get(backupPath));
return encryptor.decrypt(encryptedData);
}
}
Cloud-Native Secret Management
AWS Secrets Manager
xml
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>secretsmanager</artifactId>
</dependency>
java
@Configuration
public class AwsSecretsConfig {
@Bean
public SecretsManagerClient secretsManagerClient() {
return SecretsManagerClient.builder()
.region(Region.US_WEST_2)
.credentialsProvider(DefaultCredentialsProvider.create())
.build();
}
}
AWS Secrets Manager Service
java
@Service
public class AwsSecretsManagerService {
private final SecretsManagerClient secretsClient;
public AwsSecretsManagerService(SecretsManagerClient secretsClient) {
this.secretsClient = secretsClient;
}
public String getSecret(String secretName) {
try {
GetSecretValueRequest request = GetSecretValueRequest.builder()
.secretId(secretName)
.build();
GetSecretValueResponse response = secretsClient.getSecretValue(request);
return response.secretString();
} catch (SecretsManagerException e) {
throw new RuntimeException("Unable to retrieve secret: " + secretName, e);
}
}
public void updateSecret(String secretName, String secretValue) {
try {
UpdateSecretRequest request = UpdateSecretRequest.builder()
.secretId(secretName)
.secretString(secretValue)
.build();
secretsClient.updateSecret(request);
} catch (SecretsManagerException e) {
throw new RuntimeException("Unable to update secret: " + secretName, e);
}
}
public void rotateSecret(String secretName) {
try {
RotateSecretRequest request = RotateSecretRequest.builder()
.secretId(secretName)
.build();
secretsClient.rotateSecret(request);
} catch (SecretsManagerException e) {
throw new RuntimeException("Unable to rotate secret: " + secretName, e);
}
}
}
Kubernetes Secrets
yaml
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
namespace: production
type: Opaque
data:
database-password: <base64-encoded-password>
api-key: <base64-encoded-api-key>
jwt-secret: <base64-encoded-jwt-secret>
External Secrets Operator
yaml
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
namespace: production
spec:
provider:
vault:
server: "https://vault.example.com:8200"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "my-role"
serviceAccountRef:
name: "external-secrets"
yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
namespace: production
spec:
refreshInterval: 30s
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: app-secrets
creationPolicy: Owner
data:
- secretKey: database-password
remoteRef:
key: secret/app
property: database-password
- secretKey: api-key
remoteRef:
key: secret/app
property: api-key
Sealed Secrets
yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: mysecret
namespace: production
spec:
encryptedData:
database-password: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEQAx...
template:
metadata:
name: mysecret
namespace: production
type: Opaque
Secret Management Best Practices
Secret Rotation Strategy
java
@Component
@Scheduled
public class SecretRotationService {
private final VaultTemplate vaultTemplate;
private final ApplicationEventPublisher eventPublisher;
public SecretRotationService(VaultTemplate vaultTemplate,
ApplicationEventPublisher eventPublisher) {
this.vaultTemplate = vaultTemplate;
this.eventPublisher = eventPublisher;
}
@Scheduled(cron = "0 0 2 * * ?") // Daily at 2 AM
public void rotateSecrets() {
try {
// Database password rotation
rotateDatabasePassword();
// API key rotation
rotateApiKeys();
// JWT secret rotation
rotateJwtSecret();
eventPublisher.publishEvent(new SecretsRotatedEvent());
} catch (Exception e) {
log.error("Secret rotation failed", e);
eventPublisher.publishEvent(new SecretRotationFailedEvent(e));
}
}
private void rotateDatabasePassword() {
String newPassword = generateSecurePassword();
vaultTemplate.write("secret/database", Map.of("password", newPassword));
log.info("Database password rotated");
}
private void rotateApiKeys() {
String newApiKey = generateApiKey();
vaultTemplate.write("secret/api", Map.of("key", newApiKey));
log.info("API key rotated");
}
private void rotateJwtSecret() {
String newJwtSecret = generateJwtSecret();
vaultTemplate.write("secret/jwt", Map.of("secret", newJwtSecret));
log.info("JWT secret rotated");
}
private String generateSecurePassword() {
// Secure password generation logic
return SecureRandom.getInstanceStrong()
.ints(32, 33, 126)
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
.toString();
}
}
Secret Validation
java
@Component
public class SecretValidator {
public boolean validateDatabasePassword(String password) {
return password != null &&
password.length() >= 12 &&
password.matches(".*[A-Z].*") &&
password.matches(".*[a-z].*") &&
password.matches(".*[0-9].*") &&
password.matches(".*[!@#$%^&*()].*");
}
public boolean validateApiKey(String apiKey) {
return apiKey != null &&
apiKey.length() == 32 &&
apiKey.matches("^[A-Za-z0-9]+$");
}
public boolean validateJwtSecret(String jwtSecret) {
return jwtSecret != null &&
jwtSecret.length() >= 64;
}
}
This comprehensive secret management implementation provides all necessary components for secure management of secrets in modern Spring Boot applications.