33.6 Accessing Secrets from Lambda, ECS, and EC2
Right, let’s get your code talking to the vault. Because hardcoding secrets is for amateurs and hello-world tutorials, and you’re neither. Whether you’re in a serverless Lambda, a container in ECS, or on a crusty old EC2 instance, the principle is the same: your code needs permission to ask for the secret, and then it needs to know how to ask. I’ll show you the patterns, and then we’ll gripe about the weird bits.
First, the universal truth: Identity is everything. Your code doesn’t have a username and password; it has an identity document. For Lambda, that’s its execution role. For an ECS task, it’s the task role. For EC2, it’s the instance profile role. This is non-negotiable. If you try to access a secret without the correct IAM permissions on that role, AWS will laugh in your face with a beautifully unhelpful AccessDeniedException. Let’s fix that.
The IAM Policy: Your Golden Ticket
Before you write a single line of code, sort this out. Your role needs permission to both read the secret and optionally decrypt it (if you’re using a custom KMS key). This policy grants GetSecretValue on a specific secret. Pro-tip: use the ARN, not the name, because names are for humans, ARNs are for machines.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "secretsmanager:GetSecretValue",
"Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-app/database-creds-abc123"
},
{
"Effect": "Allow",
"Action": "kms:Decrypt",
"Resource": "arn:aws:kms:us-east-1:123456789012:key/your-kms-key-id-here"
}
]
}
Notice the KMS action? That’s a classic “it worked on my machine” trap. If you encrypted the secret with AWS’s default Secrets Manager key (alias aws/secretsmanager), your role already has permission to use it. If you used your own customer-managed key, you must add the kms:Decrypt permission. I’ve lost hours to this. Don’t be me.
The Code: Lambda Function (Python)
Lambda is the easiest because its environment is pristine and the SDK is baked in. We’ll use the Boto3 library. Key best practice: cache the client and the secret outside the handler. Your function might be invoked 1000 times a second; you don’t want to fetch the secret from the API every single time. That’s slow, expensive, and will get you rate-limited.
import boto3
import os
import json
# Initialize the client outside the handler. Cold start? Yes. Warm invocations? Blazingly fast.
secrets_client = boto3.client('secretsmanager')
# Let's even cache the secret value itself. This is a bold move, but it works if your secret isn't rotating every minute.
# We'll use a simple flag to check if we've already loaded it.
cached_secret = None
def lambda_handler(event, context):
global cached_secret
if not cached_secret:
print("Secret cache empty. Fetching from Secrets Manager.")
secret_response = secrets_client.get_secret_value(
SecretId='my-app/database-creds'
)
# The secret is a string. If you stored it as JSON, you need to parse it.
cached_secret = json.loads(secret_response['SecretString'])
else:
print("Using cached secret. Hello, performance!")
# Now use your cached_secret dict to get your values
db_host = cached_secret['host']
db_user = cached_secret['username']
db_password = cached_secret['password']
# Go connect to your database or whatever
return {
'statusCode': 200,
'body': f"Connected to {db_host} as {db_user}"
}
The Code: ECS Task (Python)
This is nearly identical to Lambda. The magic is in the task definition. You assign the IAM role (taskRoleArn) that has the permissions we defined earlier. The code itself is blissfully unaware it’s running in a container. The same caching logic applies—consider loading the secret during your application’s startup routine, not on every request.
# This code snippet could be in your app's __init__.py or a config module
import boto3
import json
from flask import Flask
app = Flask(__name__)
# Initialize client. It will automatically use the ECS task's role.
secrets_client = boto3.client('secretsmanager', region_name='us-east-1')
def get_database_config():
response = secrets_client.get_secret_value(SecretId='my-app/database-creds')
config = json.loads(response['SecretString'])
return config
# Load config at app startup
db_config = get_database_config()
@app.route('/')
def hello():
return f"The database host is {db_config['host']}"
if __name__ == '__main__':
app.run()
The Code: EC2 Instance (Python)
This is where it gets a bit… traditional. The code is the same, but the environment is different. You must ensure the EC2 instance has an instance profile attached with the correct role. The Boto3 client on the instance will automatically find the instance profile’s credentials. The major pitfall here? Never, ever run this code on your local machine without setting up a profile first. It will fail spectacularly because there’s no EC2 metadata service to provide credentials.
# This will work on an EC2 instance with a proper instance profile.
# It will fail miserably on your laptop.
import boto3
client = boto3.client('secretsmanager') # Again, no credentials needed here. The instance provides them.
response = client.get_secret_value(SecretId='my-app/database-creds')
secret = response['SecretString']
print(secret)
Parameter Store: The Simpler Sibling
Everything I just showed you works exactly the same for Systems Manager Parameter Store, but you swap the client and the method. For a secure string parameter, it’s just as secure as Secrets Manager (it’s encrypted with KMS under the hood). The API call is different, and the response structure is simpler.
ssm_client = boto3.client('ssm')
response = ssm_client.get_parameter(
Name='/my-app/database/password',
WithDecryption=True # This is the crucial flag for secure strings!
)
db_password = response['Parameter']['Value']
So, why choose one over the other? Secrets Manager has built-in rotation, which is a lifesaver. Parameter Store is cheaper and has a fantastic hierarchical structure. My rule of thumb: use Parameter Store for configuration values and simple secrets. The moment you need automatic rotation for a database credential, you graduate to Secrets Manager. It’s that simple. Now go build something without putting your secrets in GitHub.