AWS Secrets Manager — Complete Implementation Guide
In this tutorial, you will learn about AWS Secrets Manager. We cover key concepts, practical examples, and best practices to help you master this topic.
AWS Secrets Manager provides centralized secret storage with automatic rotation, fine-grained IAM access control, secret versioning, and native integration with AWS services like RDS, Lambda, and ECS.
What You'll Learn
By the end of this tutorial, you will know how to store and retrieve secrets from AWS Secrets Manager, configure automatic rotation, control access with IAM policies, and integrate with AWS services.
Why It Matters
AWS Secrets Manager is the default secrets management solution for applications running on AWS. It integrates natively with AWS services and eliminates the operational overhead of running Vault.
Real-World Use
DodaTech's AWS-hosted services use Secrets Manager for all secrets. RDS databases have automatic credential rotation every 30 days, and Lambda functions access secrets via IAM roles.
AWS Secrets Manager Learning Path
flowchart LR
A[HashiCorp Vault] --> B[AWS Secrets Manager]
B --> C[Store & Retrieve]
B --> D[Rotation]
B --> E[IAM Access]
B --> F{You Are Here}
style F fill:#f90,color:#fff
Storing and Retrieving Secrets
Use the AWS SDK to store and retrieve secrets programmatically.
const {
SecretsManagerClient,
GetSecretValueCommand,
CreateSecretCommand,
UpdateSecretCommand
} = require("@aws-sdk/client-secrets-manager");
class AWSSecretsManager {
constructor(region = "us-east-1") {
this.client = new SecretsManagerClient({ region });
}
async getSecret(secretId) {
try {
const command = new GetSecretValueCommand({ SecretId: secretId });
const response = await this.client.send(command);
if (response.SecretString) {
return JSON.parse(response.SecretString);
}
throw new Error("Secret is binary, use getBinarySecret() instead");
} catch (err) {
if (err.name === "ResourceNotFoundException") {
throw new Error(`Secret ${secretId} not found`);
}
if (err.name === "AccessDeniedException") {
throw new Error(`Access denied to secret ${secretId}`);
}
throw err;
}
}
async createSecret(name, secretValue, description = "") {
const command = new CreateSecretCommand({
Name: name,
SecretString: JSON.stringify(secretValue),
Description: description
});
const response = await this.client.send(command);
console.log(`Created secret: ${name} (${response.ARN})`);
return response;
}
async updateSecret(secretId, secretValue) {
const command = new UpdateSecretCommand({
SecretId: secretId,
SecretString: JSON.stringify(secretValue)
});
const response = await this.client.send(command);
console.log(`Updated secret: ${secretId} (version ${response.VersionId})`);
return response;
}
}
const secretsManager = new AWSSecretsManager();
secretsManager.getSecret("prod/myapp/db").then(secret => {
console.log("Retrieved database credentials");
});
Automatic Rotation
Secrets Manager can automatically rotate secrets on a schedule.
// Lambda rotation function
exports.handler = async (event) => {
const arn = event.SecretId;
const token = event.ClientRequestToken;
const step = event.Step;
const { SecretsManager } = require("@aws-sdk/client-secrets-manager");
const sm = new SecretsManager();
switch (step) {
case "createSecret":
await createSecret(sm, arn, token);
break;
case "setSecret":
await setSecret(sm, arn, token);
break;
case "testSecret":
await testSecret(sm, arn, token);
break;
case "finishSecret":
await finishSecret(sm, arn, token);
break;
default:
throw new Error("Invalid step");
}
return { status: "ok" };
};
async function createSecret(sm, arn, token) {
const crypto = require("crypto");
const password = crypto.randomBytes(16).toString("hex");
await sm.putSecretValue({
SecretId: arn,
ClientRequestToken: token,
SecretString: JSON.stringify({
username: "admin",
password: password
}),
VersionStages: ["AWSPENDING"]
});
}
async function setSecret(sm, arn, token) {
// Update the database user's password to the new value
const secret = await sm.getSecretValue({
SecretId: arn,
VersionId: token,
VersionStage: "AWSPENDING"
});
const creds = JSON.parse(secret.SecretString);
// Update database password: ALTER USER admin PASSWORD 'new-password';
console.log(`Setting database password to: ${creds.password}`);
}
async function testSecret(sm, arn, token) {
// Test the new credentials work
console.log("Testing new credentials...");
}
async function finishSecret(sm, arn, token) {
// Mark the new version as current
await sm.updateSecretVersionStage({
SecretId: arn,
VersionStage: "AWSCURRENT",
MoveToVersionId: token,
RemoveFromVersionId: "AWSPENDING"
});
console.log("Rotation complete");
}
IAM Access Control
Control which applications and users can access which secrets.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": [
"arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/myapp/*"
]
},
{
"Effect": "Allow",
"Action": "secretsmanager:ListSecrets",
"Resource": "*"
}
]
}
// IAM role for ECS task
const iamPolicy = {
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Action: [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
Resource: [
"arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/myapp/*"
]
}
]
};
console.log("IAM policy for secrets access:", iamPolicy.Statement[0].Resource);
Caching Secrets
Cache secrets to reduce API calls and improve performance.
class CachedSecretsManager {
constructor(region, options = {}) {
this.client = new (require("@aws-sdk/client-secrets-manager").SecretsManagerClient)({ region });
this.cache = new Map();
this.cacheTTL = options.cacheTTL || 300000; // 5 minutes
}
async getSecret(secretId) {
const cached = this.cache.get(secretId);
if (cached && Date.now() < cached.expiresAt) {
console.log(`Cache hit for ${secretId}`);
return cached.value;
}
console.log(`Cache miss for ${secretId}, fetching from AWS`);
const command = new (require("@aws-sdk/client-secrets-manager").GetSecretValueCommand)({ SecretId: secretId });
const response = await this.client.send(command);
const value = JSON.parse(response.SecretString);
this.cache.set(secretId, {
value,
expiresAt: Date.now() + this.cacheTTL
});
return value;
}
invalidateCache(secretId) {
this.cache.delete(secretId);
console.log(`Cache invalidated for ${secretId}`);
}
}
const cachedSM = new CachedSecretsManager("us-east-1", { cacheTTL: 60000 });
cachedSM.getSecret("prod/myapp/db").then(secret => {
console.log("Retrieved with caching");
});
Integration with AWS Services
Secrets Manager integrates natively with many AWS services.
class AWSIntegrationExamples {
static ecsTaskDefinition() {
return {
family: "my-app",
taskRoleArn: "arn:aws:iam::123456789012:role/MyAppTaskRole",
executionRoleArn: "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
containerDefinitions: [
{
name: "app",
image: "myapp:latest",
secrets: [
{
name: "DB_PASSWORD",
valueFrom: "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/myapp/db-abc123:password::"
},
{
name: "DB_HOST",
valueFrom: "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/myapp/db-abc123:host::"
}
]
}
]
};
}
static lambdaEnvironmentVariable(secretArn) {
return {
Variables: {
DB_SECRET: secretArn
}
};
}
static rdsRotationConfiguration() {
return {
rotationInterval: "30d",
rotationLambdaARN: "arn:aws:lambda:us-east-1:123456789012:function:rotate-db-password",
automaticallyRotateAfterDays: 30
};
}
}
console.log("AWS integration examples:", Object.keys(AWSIntegrationExamples).length);
Common Mistakes
Not using IAM roles for access -- Hardcoding AWS credentials in the application defeats the purpose of Secrets Manager. Use IAM roles for EC2, ECS, or Lambda.
Storing entire secrets as a single string -- Store secrets as structured JSON (username/password as separate fields) for easier individual field access in ECS task definitions.
Not caching secrets -- Every GetSecretValue call costs money and adds latency. Cache secrets in memory with a TTL.
Using Secrets Manager for non-secret configuration -- Secrets Manager costs $0.40 per secret per month. Use it only for secrets, not regular configuration.
Not planning for rotation -- Applications must handle credential rotation gracefully. The database connection string changes when the password rotates.
Practice Questions
How does AWS Secrets Manager rotation work? A Lambda function performs four steps: create new secret version, set the new credentials on the target service, test them, and mark the new version as current.
How do you control access to secrets in AWS Secrets Manager? Using IAM policies that grant specific actions (GetSecretValue) on specific secret ARNs.
What is the cost model for AWS Secrets Manager? $0.40 per secret per month plus $0.05 per 10,000 API calls. Rotation Lambda calls are additional.
Challenge: Implement a secrets manager abstraction that falls back from AWS Secrets Manager to environment variables.
class HybridSecretsProvider {
constructor(region) {
this.awsSM = new (require("@aws-sdk/client-secrets-manager").SecretsManagerClient)({ region });
}
async getSecret(name) {
const envVar = process.env[name];
if (envVar) {
return JSON.parse(envVar);
}
try {
const command = new (require("@aws-sdk/client-secrets-manager").GetSecretValueCommand)({ SecretId: name });
const response = await this.awsSM.send(command);
return JSON.parse(response.SecretString);
} catch (err) {
throw new Error(`Secret ${name} not found in env or AWS`);
}
}
}
const hybrid = new HybridSecretsProvider("us-east-1");
hybrid.getSecret("DB_CREDENTIALS").then(s => console.log("Secret resolved"));
FAQ
Mini Project
Build an AWS Secrets Manager wrapper that handles caching, IAM authentication, automatic fallback to environment variables in development, and graceful handling of rotation events.
class AWSSecretsWrapper {
constructor(region, options = {}) {
this.region = region;
this.cache = new Map();
this.cacheTTL = options.cacheTTL || 300000;
this.client = new (require("@aws-sdk/client-secrets-manager").SecretsManagerClient)({ region });
}
async get(secretId) {
if (process.env.NODE_ENV === "development") {
try { return JSON.parse(process.env[secretId]); } catch {}
}
const cached = this.cache.get(secretId);
if (cached && Date.now() < cached.expiresAt) return cached.value;
const cmd = new (require("@aws-sdk/client-secrets-manager").GetSecretValueCommand)({ SecretId: secretId });
const res = await this.client.send(cmd);
const value = JSON.parse(res.SecretString);
this.cache.set(secretId, { value, expiresAt: Date.now() + this.cacheTTL });
return value;
}
}
const wrapper = new AWSSecretsWrapper("us-east-1");
console.log("AWS Secrets Manager wrapper ready");
What's Next
Now that you understand AWS Secrets Manager, learn about Kubernetes ConfigMaps and Secrets. Then explore feature flags.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro