In the Roman Empire, the most important information — battle plans, treasury locations, legion passwords — was transmitted via encrypted dispatches. Only authorized commanders could read them. In production applications, secrets management serves the same role — it protects passwords, API keys, and certificates from unauthorized access.
Many developers store secrets in
.env files — for development this is acceptable, but in production it is a serious risk:1// Problems with .env files in production
2const envProblems = {
3 leak: '.env file accidentally committed to repository',
4 noRotation: 'Manual password changes on every server',
5 noAudit: 'No record of who accessed secrets and when',
6 noEncryption: 'Secrets stored as plain text on disk',
7 scaling: 'Copying .env to every new server',
8};
9
10// GitHub Leaked Secrets (2023):
11// > 12 million secrets leaked from public repositoriesHashiCorp Vault is the most popular secrets management tool. It works like the imperial treasury (aerarium) — centrally storing and controlling access to secrets:
1// How Vault manages secrets
2interface VaultConcepts {
3 engine: 'Secret Engine — storage backend (KV, Database, PKI)';
4 policy: 'Policy — access rules (who can read what)';
5 token: 'Token — authorization key (like the emperor\'s seal)';
6 lease: 'Lease — secret lifetime (automatic rotation)';
7 audit: 'Audit Log — complete access log';
8}
9
10// Example Vault API usage
11// vault kv put secret/roman-api DATABASE=mongodb://... JWT_SECRET=...
12// vault kv get secret/roman-api1// config/vault.config.ts
2import { registerAs } from '@nestjs/config';
3import Vault from 'node-vault';
4
5const vault = Vault({
6 endpoint: process.env.VAULT_ADDR || 'http://127.0.0.1:8200',
7 token: process.env.VAULT_TOKEN,
8});
9
10export default registerAs('secrets', async () => {
11 const result = await vault.read('secret/data/roman-api');
12 return {
13 database: result.data.data.DATABASE,
14 jwtSecret: result.data.data.JWT_SECRET,
15 redisPassword: result.data.data.REDIS_PASSWORD,
16 };
17});
18
19// app.module.ts — asynchronous loading
20@Module({
21 imports: [
22 ConfigModule.forRoot({
23 isGlobal: true,
24 load: [vaultConfig],
25 }),
26 ],
27})
28export class AppModule {}For applications hosted on AWS, Secrets Manager provides native integration:
1// config/aws-secrets.ts
2import {
3 SecretsManagerClient,
4 GetSecretValueCommand,
5} from '@aws-sdk/client-secrets-manager';
6
7const client = new SecretsManagerClient({ region: 'eu-central-1' });
8
9export async function getSecrets(): Promise<Record<string, string>> {
10 const command = new GetSecretValueCommand({
11 SecretId: 'roman-api/production',
12 });
13
14 const response = await client.send(command);
15 return JSON.parse(response.SecretString);
16}
17
18// Usage in NestJS
19export default registerAs('aws', async () => {
20 const secrets = await getSecrets();
21 return {
22 database: secrets.DATABASE,
23 jwtSecret: secrets.JWT_SECRET,
24 };
25});In Kubernetes, we store secrets as Secret objects, encoded in base64:
1# k8s/secret.yaml
2apiVersion: v1
3kind: Secret
4metadata:
5 name: roman-api-secrets
6 namespace: production
7type: Opaque
8data:
9 DATABASE: bW9uZ29kYjovL2ltcGVyYXRvcjpyb21hQG1vbmdvZGI6MjcwMTcvaW1wZXJpdW0=
10 JWT_SECRET: c3VwZXItc2VjcmV0LWtleS1taW4tMTYtY2hhcnM=Sealed Secrets allow you to safely store secrets in a Git repository — they are encrypted with the cluster's public key:
1# Install kubeseal
2brew install kubeseal
3
4# Encrypt secrets
5kubeseal --format yaml < secret.yaml > sealed-secret.yaml
6
7# Only the cluster can decrypt sealed-secret.yaml
8# It is safe to commit to the repositoryRegular secret rotation minimizes the risk of leaks:
1// Secret rotation strategy
2interface RotationStrategy {
3 secret: string;
4 interval: string;
5 method: string;
6}
7
8const rotationPolicies: RotationStrategy[] = [
9 {
10 secret: 'DATABASE_PASSWORD',
11 interval: 'Every 90 days',
12 method: 'Vault dynamic secrets — automatic rotation',
13 },
14 {
15 secret: 'JWT_SECRET',
16 interval: 'Every 30 days',
17 method: 'Vault KV v2 — versioning + graceful rotation',
18 },
19 {
20 secret: 'API_KEYS',
21 interval: 'Every 60 days',
22 method: 'AWS Secrets Manager — automatic Lambda rotation',
23 },
24];1// config/secrets.provider.ts
2import { ConfigModuleOptions } from '@nestjs/config';
3
4export const secretsConfig: ConfigModuleOptions = {
5 isGlobal: true,
6 load: [
7 // Priority 1: Vault (production)
8 async () => {
9 if (process.env.VAULT_ADDR) {
10 return loadFromVault();
11 }
12 return {};
13 },
14 // Priority 2: AWS Secrets Manager
15 async () => {
16 if (process.env.AWS_REGION) {
17 return loadFromAWS();
18 }
19 return {};
20 },
21 // Priority 3: Environment variables (fallback)
22 () => ({
23 database: process.env.DATABASE,
24 jwtSecret: process.env.JWT_SECRET,
25 }),
26 ],
27};
28
29// app.module.ts
30@Module({
31 imports: [ConfigModule.forRoot(secretsConfig)],
32})
33export class AppModule {}Secrets management is the art of protecting the Empire's secret dispatches. Never leave secrets in .env files in production — use dedicated tools: Vault, AWS Secrets Manager, or Kubernetes Secrets.