Access SSM parameters from parameter store - amazon-web-services

I have SAM template which deploys few lambdas and I would like to use some parameters I created in SSM parameters store.
I created 2 parameters for my tests:
/test/param which is a simple string
/test/param/encrypt which contains the same string as /test/param but is encrypted by a KMS key
In my SAM template, I'm trying to get the the value of /test/params by following this blog post. Here is a snipper of my template:
Parameters:
AuthPasswordPublic:
Type: AWS::SSM::Parameter::Value<String>
NoEcho: true
MinLength: 8
Description: Password for the "public" part of the website
Default: /test/param
...
Resources:
Auth:
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs8.10
Handler: auth.handler
CodeUri: ./dist
Environment:
Variables:
PASSWORD_PUBLIC: !Ref AuthPasswordPublic
SEED: !Ref AuthSeed
Events:
GetResource:
Type: Api
Properties:
Path: /auth
Method: post
This should theoretically works when deployed onto AWS. However, I would like to test it locally first. I'm already aws-sam-local and my credentials are properly configured on my local machine as I'm able to use the AWS CLI. But when running this locally, the value of the envvar PASSWORD_PUBLIC is empty. I tested both the plain text en encrypted SSM parameters but the results are the same.
I would suspect that aws-sam-cli does not support SSM parameters yet but couldn't find any information about that online, nor on the GitHub issues/PR. Any ideas of what is going on here?

aws-sam-cli uses the docker-lambda container, which according to the docs creates:
A sandboxed local environment that replicates the live AWS Lambda environment almost identically...
This means that components such as AWS SSM are not re-created within the docker container. You can check the open Github issue here.
So you may have to resort to retrieving the SSM parameters from the host (with aws cli configured), and pass them into the container when invoking sam-cli:
PASSWORD_PUBLIC=$(aws ssm get-parameter --with-decryption --name "/test/param/encrypt") sam local start-api

Related

using parameter store values in apprunner

I'm using aws copilot but i think i can generalize this question to apprunner. trying to get envvars set from Parameter Store but having no luck. the left is my aws copilot manifest yml, i saw examples of setting things this way. it results in an apprunner configed on the right. in production it seems these are interpreted as literals and not as parameter store values
any idea on how to properly connect apprunner to parameter store?
Unfortunately currently App Runner doesn't provide an intrinsic way for integrating with SSM parameter store like what ECS does. As a result, Copilot doesn't support secret section for Request-driven service as well (refer to Copilot doc here). As for environment variables, they are what you define in the manifest and will be injected as literals.
However, there is a workaround in Copilot allowing your app to use secrets stored in SSM parameter store. You can specify an addon template (e.g., policy.yaml) and put it in the copilot/${svc name}/addons/ local directory with the following template allowing the App Runner service to be able to retrieve from SSM parameter store:
Parameters:
App:
Type: String
Description: Your application's name.
Env:
Type: String
Description: The environment name your service, job, or workflow is being deployed to.
Name:
Type: String
Description: The name of the service, job, or workflow being deployed.
Resources:
MySSMPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
PolicyDocument:
Version: 2012-10-17
Statement:
- Sid: SSMActions
Effect: Allow
Action:
- "ssm:GetParameters"
Resource: !Sub 'arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*'
Outputs:
MySSMPolicyArn:
Value: !Ref MySSMPolicy
After that, in your code by using AWS SDK you can call AWS SSM API to retrieve any secrets you defined before. Let me know if you have any more questions!

Running existing AWS Lambdas locally

I've created AWS Lambda in C# using Visual Studio that returns some JSON from the API endpoint. Now I want to run that lambda locally. All the examples use AWS SAM, but they create a new function using the SAM template.
When I run the command sam local start-lambda I get an error saying that the template isn't found. So what is certain is that I need template.yaml, but I'm not sure is there a way to generate this template for existing Lambda?
Any help is appreciated!
Check out the Template Anatomy resource on the AWS documentation.
You might find this example helpful (it's greatly simplified). I use NodeJS for development, but the differences between programming languages when you're creating a SAM Template are trivial. The example is an outline for a simple Lambda function someFunction being invoked by an API Gateway (HTTP) event.
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: My Serverless Application
Parameters:
# Manually define this in AWS IAM for just the services needed.
lambdaExecutionRole:
Description: 'Required. The role used for lambda execution.'
Type: 'String'
Default: 'arn:aws:iam::nnnnnnnnnnnn:role/LambdaExecutionRole'
Globals:
Function:
Runtime: nodejs10.x
# Environment:
# Variables:
# NODE_ENV: test
# DEBUG: myapp:foo
Resources:
performSomeFunction:
Type: 'AWS::Serverless::Function'
Properties:
FunctionName: performSomeFunction
Handler: lambda.someFunction
CodeUri: ./
Description: description of the function being performed
MemorySize: 256
Timeout: 60
Role:
Ref: lambdaExecutionRole
Events:
# API Gateway proxy endpoint.
ProxyApiRoot:
Type: Api
Properties:
Path: '/'
Method: ANY
ProxyApiGreedy:
Type: Api
Properties:
Path: '/{proxy+}'
Method: ANY
As you're getting started with AWS Lambda, one of the big concepts to keep in mind is how your function will be triggered. Functions are triggered by different kinds of events, and there can be many many different types of events. I tend to use API Gateway, Simple Queue Service and CloudWatch Events to trigger mine, but it entirely depends on your use case.
It turned out that you can export Lambda function, and get the generated .yaml template, which was exactly what I needed.

Can I force CloudFormation to resolve values from Secrets Manager?

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.
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.
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.
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...).

How to fetch latest version of SSM parameter on Cloudformation Yaml file

I have a Cloudformation Yaml file where it creates a lot of resources, one of them is an EC2 with Windows. My problem is I need to have this automated selection of the latest AMI, I made a Lambda function where it retrieves newest AMI ID and stores in an SSM parameter, then using the Cloudformation with YAML template I can access to ssm using the following command
{{resolve:ssm:parameter_name:version_number_int}}
but my problem is that it's not always the same version number, it will be changed everytime when there will be a new AMI, is there any method where I can write to get always the latest version? or to stop versioning or anything?
Thanks.
SSM Parameter Store provides public parameters to retrieve latest AMIs.
# Use public Systems Manager Parameter
Parameters:
LatestAmiId:
Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
Default: '/aws/service/ami-windows-latest/Windows_Server-2019-English-Full-Base'
Resources:
Instance:
Type: 'AWS::EC2::Instance'
Properties:
ImageId: !Ref LatestAmiId

AWS Serverless, CloudFormation : Error, Trying to populate non string value into a string for variable

I am using serverless framework for deploying my application on AWS Cloud.
https://serverless.com/
I want to use the value of AWS Account ID in my serverless.yml file and I want to export the acccount ID as the environment variable, so that it can be accessed from Lambda functions.
Based on the value of this lambda function, I want to create some resources ( like IAM roles, etc.), which refer to this accountId variable.
But when I try to deploy the stack, I get the below error,
Trying to populate non string value into a string for variable
${self:custom.accountId}. Please make sure the value of the property
is a string.
My Serverless.yml file is as below
custom:
accountId : !Ref "AWS::AccountId"
provider:
name: aws
runtime: go1.x
stage: dev
region: us-east-1
environment:
ACCOUNT_ID : ${self:custom.accountId}
myRoleArn: arn:aws:iam::${self:custom.accountId}:role/xxxxxxxx
Is there any way to refer to the value of the Account Id in the serverless.yml file?
You can't reference AWS::AccountId in your serverless.yml, because it doesn't quite translate when creating the CloudFormation template.
The workaround is to use the serverless plugin Pseudo Parameters.
You can install the plugin using npm.
npm install serverless-pseudo-parameters
You will also need to add the plugin to the serverless.yml file.
plugins:
- serverless-pseudo-parameters
Then you can reference your AccountId with #{AWS::AccountId}
functions:
helloWorld:
handler: index.handler
events:
- http:
path: /
method: get
environment:
ACCOUNT_ID : #{AWS::AccountId}
Note that the reference begins with a hash instead of a dollar sign.