Reputation: 38346
In the following (abbreviated CloudFormation template), I am trying to configure an AWS Lambda function to get a value from AWS Secrets Manager injected into its environment:
Resources:
Function:
Type: AWS::Serverless::Function
Properties:
Environment:
Variables:
SECRET_KEY: !Sub '{{resolve:secretsmanager:${Secret}:SecretString:KEY}}'
Secret:
Type: AWS::SecretsManager::Secret
Properties:
Name: 'very-secret-thing'
SecretString: '{"KEY":"dummy"}'
When creating a stack using this template, everything comes up as expected. I then go and change the value of the secret outside of CloudFormation, as I would not really want the secret checked into source control. This is totally possible, and the documentation implies, that the secret's value will not be touched subsequent CloudFormation stack updates, as long as I avoid changing the dummy value for SecretString
in the template.
So, after setting the actual secret in the AWS Console, I need to trigger a redeploy of the Lambda function, so that the new secret value will be resolved by CloudFormation and set in the function's environment. How do I do that?
Executing aws cloudformation deploy
fails with the message: No changes to deploy.
I suspect CloudFormation is comparing the "raw" version of the template with what was deployed last, without first resolving the references to Secrets Manager. Is that the case? And is there some trick to force earlier dereferencing?
Footnote: I am well aware that using Secrets Manager this way will cause the secret value to be visible in the AWS Lambda Console, and that getting the value from Secrets Manager at runtime would be the more secure approach. That just happens to be out-of-scope for what I am hoping to do.
Upvotes: 10
Views: 12064
Reputation: 763
Just to add for other readers who find this page, AWS published a solution to reference the version ID of the secret at the end of the key.
Resources:
SG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: '{{resolve:secretsmanager:mysecret:SecretString:MyKey::ab01234c-5d67-89ef-01gh-2ijk345l6m78}}'
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
CidrIp: 0.0.0.0/0
Now the thing is, you'll apparently have to do an API call to figure out what the versionId is since it's not on the console (which doesn't make any sense but it's AWS...).
Upvotes: 4
Reputation: 516
You can artificially change something else on the AWS::Serverless::Function
resource to force it to update when you do your deployment.
Say, for example, a timestamp:
Parameters:
DeployTimestamp: { Type: String }
Resources:
Function:
Type: AWS::Serverless::Function
Properties:
Environment:
Variables:
SECRET_KEY: !Sub '{{resolve:secretsmanager:${Secret}:SecretString:KEY}}'
SECRET_KEY_UPDATED: !Ref DeployTimestamp
Assuming you do your deployment from a script, then you'd do something like aws cloudformation deploy --parameter-overrides "DeployTimestamp=$(date)"
to change the value each time.
The downside to this, of course, is that the function will update every deployment, even if the secret hasn't updated. If that bothers you, you could get fancier and inject aws secretsmanager describe-secret --query LastChangedDate
as a parameter instead of the current time.
Upvotes: 19
Reputation: 1623
You say that reading the value in the Lambda is out of scope, but that is really the correct solution. It not only improves security, but allows the Lambda to pickup the latest value when the secret is rotated.
If you read the secret outside the handler (that is to say, during initialization), the number of reads is minimized. If this is a java lambda connecting to a database, you could also use the secrets manager jdbc wrapper which will automatically fetch the secret.
Upvotes: 1