Secrets Management — Complete Implementation Guide
In this tutorial, you will learn about Secrets Management. We cover key concepts, practical examples, and best practices to help you master this topic.
Secrets management is the practice of securely storing, accessing, and rotating sensitive configuration values like passwords, API keys, and certificates, keeping them separate from code and configuration files.
What You'll Learn
By the end of this tutorial, you will know how to identify secrets, choose between environment variables and secrets managers, implement encryption, and rotate secrets safely.
Why It Matters
Secrets leakage is one of the most common security breaches. Hardcoded secrets in code, config files, or Docker images are a primary attack vector for data breaches.
Real-World Use
DodaTech uses AWS Secrets Manager for all secrets. No secrets exist in code, config files, or Docker images. Every secret has automated rotation with 90-day intervals.
Secrets Management Learning Path
flowchart LR
A[Configuration Hierarchy] --> B[Secrets Management]
B --> C[Identifying Secrets]
B --> D[Storage Options]
B --> E[Rotation]
B --> F{You Are Here}
style F fill:#f90,color:#fff
Identifying Secrets
Not all configuration is a secret. Learn to distinguish secrets from non-sensitive config.
class SecretIdentifier {
static isSecret(key, value) {
const secretPatterns = [
/password/i, /secret/i, /token/i, /key/i, /credential/i,
/certificate/i, /pem/i, /jwt/i, /auth/i, /api[_-]?key/i,
/private/i, /access[_-]?key/i, /secret[_-]?key/i
];
const matchesPattern = secretPatterns.some(p => p.test(key));
const containsSensitive = value && (
value.length > 20 ||
value.includes("-----BEGIN") ||
/^[A-Za-z0-9+/=]{20,}$/.test(value)
);
return { isSecret: matchesPattern || containsSensitive, reason: matchesPattern ? "key pattern" : containsSensitive ? "value pattern" : "not sensitive" };
}
static classify(config) {
const secrets = {};
const nonSecrets = {};
for (const [key, value] of Object.entries(config)) {
const { isSecret } = this.isSecret(key, value);
if (isSecret) {
secrets[key] = "***hidden***";
} else {
nonSecrets[key] = value;
}
}
return { secrets, nonSecrets };
}
}
const config = {
DB_HOST: "localhost",
DB_PASSWORD: "super-secret-password-123",
PORT: 3000,
JWT_SECRET: "abc123def456ghi789",
LOG_LEVEL: "info"
};
const classified = SecretIdentifier.classify(config);
console.log("Secrets:", Object.keys(classified.secrets).join(", "));
console.log("Non-secrets:", Object.keys(classified.nonSecrets).join(", "));
// Secrets: DB_PASSWORD, JWT_SECRET
// Non-secrets: DB_HOST, PORT, LOG_LEVEL
Environment Variable Secrets
The simplest approach: inject secrets as environment variables.
class EnvVarSecrets {
static load() {
const requiredSecrets = [
"DB_PASSWORD",
"JWT_SECRET",
"API_KEY"
];
const missing = requiredSecrets.filter(s => !process.env[s]);
if (missing.length > 0) {
throw new Error(
`Missing required secrets: ${missing.join(", ")}\n` +
`Set them as environment variables or use a secrets manager.`
);
}
return {
dbPassword: process.env.DB_PASSWORD,
jwtSecret: process.env.JWT_SECRET,
apiKey: process.env.API_KEY
};
}
static securityCheck() {
const risks = [];
if (process.env.DB_PASSWORD?.length < 12) {
risks.push("DB_PASSWORD is too short (min 12 characters)");
}
if (process.env.JWT_SECRET?.length < 32) {
risks.push("JWT_SECRET is too short (min 32 characters)");
}
return { safe: risks.length === 0, warnings: risks };
}
}
// In production, env vars set by orchestrator
// docker run -e DB_PASSWORD=... -e JWT_SECRET=... myapp
// kubectl create secret generic app-secrets --from-literal=DB_PASSWORD=...
const secrets = EnvVarSecrets.load();
const check = EnvVarSecrets.securityCheck();
console.log("Secrets loaded, security check:", check.safe ? "passed" : "warnings");
File-Based Secrets
Some platforms inject secrets as files mounted into the container.
class FileBasedSecrets {
constructor(secretsDir = "/run/secrets") {
this.secretsDir = secretsDir;
}
load(name) {
const fs = require("fs");
const path = require("path");
const filePath = path.join(this.secretsDir, name);
try {
const value = fs.readFileSync(filePath, "utf8").trim();
console.log(`Loaded secret: ${name} from ${filePath}`);
return value;
} catch (err) {
if (err.code === "ENOENT") {
console.warn(`Secret file not found: ${filePath}`);
return null;
}
throw err;
}
}
loadAll(secretNames) {
const secrets = {};
for (const name of secretNames) {
const value = this.load(name);
if (value !== null) {
secrets[name] = value;
}
}
return secrets;
}
}
// Docker Swarm secrets or Kubernetes secrets mounted as files
// docker run --secret db_password myapp
// Secrets are mounted at /run/secrets/db_password
const fileSecrets = new FileBasedSecrets("/run/secrets");
const secrets = fileSecrets.loadAll(["db_password", "jwt_secret", "api_key"]);
Encryption at Rest and in Transit
Secrets must be encrypted at rest (storage) and in transit (network).
class SecretsEncryption {
static async encrypt(value, key) {
const crypto = require("crypto");
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
let encrypted = cipher.update(value, "utf8", "hex");
encrypted += cipher.final("hex");
const authTag = cipher.getAuthTag().toString("hex");
return { encrypted, iv: iv.toString("hex"), authTag };
}
static async decrypt(encrypted, iv, authTag, key) {
const crypto = require("crypto");
const decipher = crypto.createDecipheriv(
"aes-256-gcm",
key,
Buffer.from(iv, "hex")
);
decipher.setAuthTag(Buffer.from(authTag, "hex"));
let decrypted = decipher.update(encrypted, "hex", "utf8");
decrypted += decipher.final("utf8");
return decrypted;
}
static explain() {
return {
atRest: "Secrets stored in encrypted format (Vault, AWS Secrets Manager, encrypted files)",
inTransit: "TLS for API calls to secrets manager, HTTPS for all secret transport",
inUse: "Secrets decrypted in memory only, never written to disk unencrypted"
};
}
}
console.log("Encryption requirements:", SecretsEncryption.explain());
// Encryption requirements: { atRest: "...", inTransit: "...", inUse: "..." }
Secrets Rotation
Regular secrets rotation limits the impact of a compromised secret.
class SecretsRotation {
static createRotationPolicy() {
return {
rotationInterval: 90, // days
gracePeriod: 24, // hours old secret is still valid after rotation
notificationDays: [14, 7, 1], // days before rotation to notify
requiredSecrets: [
{ name: "DB_PASSWORD", rotationStrategy: "generate-new" },
{ name: "JWT_SECRET", rotationStrategy: "generate-new" },
{ name: "API_KEY", rotationStrategy: "regenerate-at-provider" }
]
};
}
static async rotateSecret(name, currentValue, secretStore) {
console.log(`Rotating secret: ${name}`);
const crypto = require("crypto");
const newValue = crypto.randomBytes(32).toString("hex");
// Update the secret store
await secretStore.put(name, newValue);
// Keep old value valid during grace period
console.log(` New value stored for ${name}`);
console.log(` Old value will expire in 24 hours`);
return { name, rotated: true, effectiveImmediately: true };
}
static generateSecret(length = 32) {
const crypto = require("crypto");
return crypto.randomBytes(length).toString("base64");
}
}
const newSecret = SecretsRotation.generateSecret(32);
console.log("Generated secret:", newSecret.substring(0, 10) + "...");
// Generated secret: aB3xK9mP...
Common Mistakes
Checking secrets into version control -- The most common and dangerous mistake. Use .gitignore, pre-commit hooks, and secret scanners to prevent this.
Logging secrets -- Never log secret values. Sanitize logs to replace secrets with [REDACTED]. Use structured logging with automatic redaction.
Using the same secret across environments -- Development, staging, and production should each have unique secrets. A compromised dev secret should not affect production.
Hardcoding fallback values for secrets -- Process.env.DB_PASSWORD || "password" creates a security risk. Required secrets should fail at startup if missing.
Not rotating secrets regularly -- Static secrets increase breach impact. Rotate secrets every 90 days or less.
Practice Questions
What is the difference between configuration and secrets? Configuration includes non-sensitive settings. Secrets include sensitive data that needs encryption and access control.
What are three ways to inject secrets into a container? Environment variables, mounted files, and secrets manager API calls.
Why should secrets be rotated regularly? To limit the impact of a compromised secret. If a secret is leaked, rotation ensures it's only valid for a limited time.
Challenge: Implement a secrets manager abstraction that supports multiple backends (env vars, file, AWS Secrets Manager).
class SecretsManagerAbstraction {
constructor() {
this.backends = [];
}
addBackend(name, loader) {
this.backends.push({ name, loader });
}
async getSecret(name) {
for (const backend of this.backends) {
try {
const value = await backend.loader(name);
if (value !== null && value !== undefined) {
return value;
}
} catch {
continue;
}
}
throw new Error(`Secret ${name} not found in any backend`);
}
}
const sm = new SecretsManagerAbstraction();
sm.addBackend("env", (name) => process.env[name]);
sm.addBackend("file", (name) => { try { return require("fs").readFileSync(`/run/secrets/${name}`, "utf8").trim(); } catch { return null; } });
sm.getSecret("DB_PASSWORD").then(v => console.log("Secret resolved"));
FAQ
Mini Project
Build a secrets management module that supports multiple backends (environment variables, file-based, AWS Secrets Manager), encrypts secrets in memory, validates secret strength, and alerts on upcoming rotation.
class SecretsModule {
constructor() {
this.secrets = {};
this.loaded = false;
}
async initialize() {
this.secrets.dbPassword = process.env.DB_PASSWORD;
this.secrets.jwtSecret = process.env.JWT_SECRET;
this.secrets.apiKey = process.env.API_KEY;
this.loaded = true;
this.validate();
}
validate() {
const missing = Object.entries(this.secrets).filter(([, v]) => !v).map(([k]) => k);
if (missing.length > 0) {
throw new Error(`Missing secrets: ${missing.join(", ")}`);
}
}
get(name) {
if (!this.loaded) throw new Error("Secrets not initialized");
return this.secrets[name];
}
}
const secrets = new SecretsModule();
secrets.initialize().then(() => console.log("Secrets module ready"));
What's Next
Now that you understand secrets management, learn about HashiCorp Vault for centralized secrets management. Then explore AWS Secrets Manager.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro