1. Spring Boot with Vault
Spring Cloud Vault
Spring Cloud Vault integrates Vault as a PropertySource in Spring Boot — secrets from Vault automatically bind to application properties.
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.vault</groupId>
<artifactId>spring-vault-core</artifactId>
</dependency>
Configuration
# application.yml
spring:
cloud:
vault:
uri: https://vault.company.com:8200
authentication: KUBERNETES
kubernetes:
role: webapp
kubernetes-path: auth/kubernetes
service-account-token-file: /var/run/secrets/kubernetes.io/serviceaccount/token
kv:
enabled: true
backend: secret
default-context: production/webapp
profile-separator: /
database:
enabled: true
role: webapp-db
backend: database
config:
lifecycle:
enabled: true
min-renewal: 10s
expiry-threshold: 1m
AppRole Authentication
spring:
cloud:
vault:
uri: https://vault.company.com:8200
authentication: APPROLE
app-role:
role-id: ${VAULT_ROLE_ID}
secret-id: ${VAULT_SECRET_ID}
app-role-path: auth/approle
Using Secrets in Code
// Secrets từ Vault tự động binding vào @Value
@Service
public class DatabaseService {
@Value("${db.username}")
private String dbUsername;
@Value("${db.password}")
private String dbPassword;
@Value("${api.key}")
private String apiKey;
}
// Hoặc dùng @ConfigurationProperties
@Configuration
@ConfigurationProperties(prefix = "db")
public class DatabaseConfig {
private String host;
private int port;
private String username;
private String password;
// getters, setters
}
Dynamic Database Credentials
@Configuration
public class VaultDatabaseConfig {
@Bean
public DataSource dataSource(
SecretLeaseContainer leaseContainer,
@Value("${spring.datasource.url}") String url) {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(url);
// Lắng nghe secret rotation
leaseContainer.addLeaseListener(event -> {
if (event.getSource().getPath().equals("database/creds/webapp-db")) {
if (event instanceof SecretLeaseExpiredEvent ||
event instanceof SecretLeaseCreatedEvent) {
Map<String, Object> secrets = event.getSource().getSecrets();
dataSource.setUsername((String) secrets.get("username"));
dataSource.setPassword((String) secrets.get("password"));
// Soft evict connections
dataSource.getHikariPoolMXBean()
.softEvictConnections();
}
}
});
return dataSource;
}
}
Transit Encryption trong Java
@Service
public class EncryptionService {
private final VaultTransitOperations transit;
public EncryptionService(VaultTemplate vaultTemplate) {
this.transit = vaultTemplate.opsForTransit();
}
public String encrypt(String plaintext) {
return transit.encrypt("my-app-key", plaintext);
}
public String decrypt(String ciphertext) {
return transit.decrypt("my-app-key", ciphertext);
}
// Batch encryption
public List<VaultEncryptionResult> encryptBatch(List<String> plaintexts) {
List<Plaintext> batch = plaintexts.stream()
.map(Plaintext::of)
.collect(Collectors.toList());
return transit.encrypt("my-app-key", batch);
}
}
2. Node.js with Vault
node-vault Client
// npm install node-vault
const vault = require('node-vault')({
apiVersion: 'v1',
endpoint: process.env.VAULT_ADDR || 'https://vault.company.com:8200',
});
// Login với AppRole
async function login() {
const result = await vault.approleLogin({
role_id: process.env.VAULT_ROLE_ID,
secret_id: process.env.VAULT_SECRET_ID,
});
vault.token = result.auth.client_token;
return result;
}
// Đọc KV secret
async function getSecret(path) {
const result = await vault.read(`secret/data/${path}`);
return result.data.data;
}
// Sinh database credentials
async function getDatabaseCreds(role) {
const result = await vault.read(`database/creds/${role}`);
return {
username: result.data.username,
password: result.data.password,
leaseId: result.lease_id,
leaseDuration: result.lease_duration,
};
}
// Encrypt với Transit
async function encrypt(plaintext) {
const result = await vault.write('transit/encrypt/my-key', {
plaintext: Buffer.from(plaintext).toString('base64'),
});
return result.data.ciphertext;
}
// Main
(async () => {
await login();
const dbConfig = await getSecret('production/db');
console.log(`Connecting to ${dbConfig.host}:${dbConfig.port}`);
})();
Kubernetes Auth from Node.js
const fs = require('fs');
async function k8sLogin() {
const jwt = fs.readFileSync(
'/var/run/secrets/kubernetes.io/serviceaccount/token',
'utf8'
);
const result = await vault.kubernetesLogin({
role: 'webapp',
jwt: jwt,
});
vault.token = result.auth.client_token;
return result;
}
3. Python with hvac
# pip install hvac
import hvac
client = hvac.Client(url='https://vault.company.com:8200')
# AppRole login
client.auth.approle.login(
role_id=os.environ['VAULT_ROLE_ID'],
secret_id=os.environ['VAULT_SECRET_ID'],
)
# KV v2
secret = client.secrets.kv.v2.read_secret_version(
path='production/db',
mount_point='secret',
)
db_password = secret['data']['data']['password']
# Database dynamic credentials
creds = client.secrets.database.generate_credentials(
name='app-role',
mount_point='database',
)
print(f"Username: {creds['data']['username']}")
print(f"Password: {creds['data']['password']}")
# Transit encrypt
result = client.secrets.transit.encrypt_data(
name='my-key',
plaintext=base64.b64encode(b'sensitive data').decode(),
)
ciphertext = result['data']['ciphertext']
4. Application Patterns
| Pattern | How it works | Advantages | Disadvantages |
|---|---|---|---|
| Direct API | App calls Vault API directly | Full control | App must know Vault, handle renewal |
| Agent Sidecar | Vault Agent render files | App reads files, unknown Vault | Sidecar overhead |
| Env Injection | Secrets inject into env vars | Simple, universal | Static, no auto-rotate |
| CSI Volume | Secrets mount to volume | Native K8s, no sidecar | Restrict dynamic secrets |
| VSO | Operator sync into K8s Secret | Recommended, auto-refresh | Secret in K8s etcd |
5. Secret Zero Problem
"Secret Zero" is a paradox: to get secrets from the Vault, a first secret is needed for authentication. Solutions:
Platform Identity: Kubernetes SA, AWS IAM Role, Azure Managed Identity → no secret needed
Response Wrapping: Orchestrator generates wrapped SecretID, assigned to app. App unwrap once
CI/CD OIDC: GitHub Actions/GitLab CI OIDC tokens → JWT auth, no static secrets
6. Summary
Spring Cloud Vault — deepest integration, auto PropertySource binding, dynamic credential rotation
Node.js (node-vault) — flexible client library, async/await API
Python (hvac) — comprehensive client for scripting and applications
Platform Identity — solve the Secret Zero problem most effectively
The next article will learn how to integrate Vault with Terraform, Ansible and CI/CD Pipelines — Infrastructure as Code meets Secret Management.