What's your secret? Parameter Store!
The secret of AWS Parameter Store
· 5 min read
The problem #
I have one, something related to transparency. And to finance.
That I needed to create my own expense tracker app.
The app utilizes is powered primarily by Supabase and AWS, which I will expand further on future posts.
The actual problem, really #
The app uses AWS Lambda and is written in Golang. It is what powers the backend. It is what I use as the main compute engine for the Rest API service. It has services related to auth and the usual CRUD operations. So it needs access to sensitive data like database connection strings and auth-related secrets. And guess where those were initially stored?
I have known (maybe not) from the beginning that Lambda environment variables was not the most appropriate place to house secrets, but I needed a quick way just to build the basic features of the app, the first version so to speak. And no one else has access to the account anyway.
Now that the basic features are done, it is time to change how we do things.
The secure way.
PS #
Enter AWS Systems Manager Parameter Store (PS).
This is an AWS service, one of the two (that I know) that you can use to securely store secrets. The other AWS service catering specifically to secrets management is the aptly named Secrets Manager which provides more features like automatic rotation.
Being a more general-purpose configuration value store, Parameter Store has less features and does not have automatic rotation functionality. This means PS is a lot cheaper than Secrets Manager. In fact, there is a Standard Tier in PS which is completely free. This is perfect for personal projects and broke devs.
How to use and integrate Parameter Store? #
Creating a parameter #
Creating a parameter in PM is easy.
- From AWS Console, go to Systems Manager.
- Click Parameter Store from the left sidebar.
- Click Create Parameter.
- Enter a name for your secret in Name field. You can also enter a path.
- For Tier, I chose Standard since it is free.
- Since I am using PM as secrets storage, the Type will be SecureString.
- For KMS key source and KMS Key ID, I left it as default. (in the future, I will create KMS key since KMS has generous forever free tier.)
- Enter your secret value in Value field.
- Click Create Parameter.
Now, you can start getting your secrets from Parameter Store. But how?
How to fetch parameters from Parameter Store? #
I found that one can use the AWS SDK for Go to access the Parameter Store service. But after further research, if you are using Lambda, you can utilize a Lambda extension called AWS Parameters and Secrets Lambda Extension to interact with PS. This requires no additional library installation and this is what I chose to use. I still needed to install the SDK though which I will elaborate later.
Some differences between using SDK and the extension #
When using the extension, the extension handles caching. When you fetch a paramater using the extension, it caches the result for future use and this improves latency. You have to do this manually using the SDK. This can also reduce cost because it reduces requests directly to Parameter store.
But in SDK, there are more APIs available that you can utilize, like the GetParametersByPath which fetches multiple parameters at once using parameter path. This is not possible using the extension and you would have to perform multiple requests to get multiple parameters.
Implementation #
You can refer to this link for more information, but here are some high-level details on how to integrate and use the extension to fetch parameters from PM.
-
You need to add the extension to your Lambda function.
-
Make sure the Lambda execution role has the ssm:GetParameter and kms:Decrypt permissions.
-
Even though everything is in Lambda environment, you still need to have an AWS session token. Because of this, I still had to install and fetch a session token using the SDK.
-
To actually fetch a parameter, you need to perform a GET request to http://localhost:2773/systemsmanager/parameters/get/?name=PARAMETER_NAME&withDecryption=true.
Yes, it is localhost.
And you need to add a request header X-Aws-Parameters-Secrets-Token and assign the AWS session token you got from step #3.Port 2773 is a default value (you can change this, refer to docs).
PARAMETER_NAME must be URL-encoded.
withDecryption=true means the response will contain the decrypted version of the parameter.
NOTE: You cannot get multiple parameters using parameter path with this extension. You have to perform multiple GET requests instead.
The function I was able to create in Golang and the output type are below.
func GetParameter(ctx context.Context, paramName string, awsSessionToken string) (ssmParameterResponse, error) {
escaped := url.QueryEscape(paramName)
parameterURL := fmt.Sprintf("http://localhost:%d/systemsmanager/parameters/get/?name=%s&withDecryption=true", PARAMETER_LAMBDA_PORT, escaped)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, parameterURL, nil)
if err != nil {
return ssmParameterResponse{}, err
}
req.Header.Add("X-Aws-Parameters-Secrets-Token", awsSessionToken)
res, err := http.DefaultClient.Do(req)
if err != nil {
return ssmParameterResponse{}, err
}
defer res.Body.Close()
var parameter ssmParameterResponse
err = json.NewDecoder(res.Body).Decode(¶meter)
if err != nil {
return ssmParameterResponse{}, err
}
return parameter, nil
}type ssmParameterResponse struct {
Parameter struct {
ARN string `json:"ARN"`
DataType string `json:"DataType"`
LastModifiedDate time.Time
Name string `json:"Name"`
Selector string `json:"Selector"`
SourceResult string `json:"SourceResult"`
Value string `json:"Value"`
Version int64 `json:"Version"`
} `json:"Parameter"`
}After all the above, I deleted all Lambda environment variables that were not supposed to be there.
Now the app is one step more secure.
…….
When I was doing this, I had to update the code and deploy to Lambda too many times!
A lot of trial-and-error. And a lot of logging!
I had to look at the Lambda logs in the CloudWatch logs for debugging.
I hope you all learned this post.