Using secrets stored in AWS Secrets Manager as environment variables for ECS container definitions. With Terraform.
Christos Alexiou
Posted on December 10, 2022
Hello readers. I hope this piece of writing finds you healthy, sitting in a comfortable sofa or desk chair, and enjoying the wintry weather of your country.
Today, we are going to discuss environment variables in task definitions.
The scenario
Given a managed cluster in AWS ECS, that was created using AWS Terraform provider, you are challenged to securely configure environment variables in the task definitions of each one of your cluster tasks, which you are also managing with Terraform - because Terraform all the things.
The manifesto
There are countless ways of handling application secrets today, probably as many as the people who write software. I am not trying to preach on whether this is the right way to do it, or if it's the most secure or comfortable. But, I know for a fact that many people will end up in situations where this piece of writing will become a useful guide.
The implementation
Directory structure
I was either lucky or farsighted when I was deciding on the structure of my Terraform code. This wonderful occasion allowed me to have the resources for the tasks, the services, and all of the paraphernalia in the same module letting me make direct references between them without worrying about inheritance or inclusion.
For this article, it is enough for the reader to be aware of the following directory structure:
├── services.tf
├── task_env_vars.tf
└── tasks. tf
├── definitions
│ └── template.json
Secrets in AWS Secrets Manager
AWS Secrets Manager helps you manage, retrieve, and rotate database credentials, API keys, and other secrets throughout their lifecycles. They encrypt the keys using the AWS Key Management Service. I suppose it is secure enough. If you are working for VISA or MasterCard and you think that the level of security they provide is not enough, you probably ought not to read this piece of writing.
I am not managing the secrets that are stored in AWS Secrets Manager with Terraform. I chose to manage those secrets using the AWS Console interface, and this is the path I've taken, although not ideal.
If the reader wants to go the route of managing the secrets with Terraform, the required documentation is here and it's extremely straightforward.
For us, plebes, I am going to continue writing with the standpoint that the secrets are created through the interface, as I did, for instance, for the secret values that I want to be used as environment variables in a service that I wrote that's responsible for mailing.
Creating a secret
When prompted by the wizard of the AWS Secrets Manager, we shall select the option "Other type of secret". This will enable us to store secrets in a key/value structure, that will prove particularly useful when handling this data for use with our task definitions.
We can fill the text boxes with whatever we fancy.
Once we are done filling all the key/value pairs for the secret, we hit "Next" to finalize and name our secret, ignoring all the other options - at least for our case.
Since I am planning to use this secret in a service that does mailing, I named it mailing-env.
Now, the secret is available to us through the AWS API and thus, the Terraform provider.
Retrieving the secret
With the secret available in AWS Secrets Manager, we can use the data source aws_secretsmanager_secret_version
to retrieve the secret.
data "aws_secretsmanager_secret_version" "mailing" {
secret_id = "mailing-env"
}
Formatting the secret's values for further use
Because the received data are encoded and do not have the proper naming conventions for us to use right away in our task definitions, we have to do some local processing, to get them into an appropriate state.
locals {
mailing_secrets = jsondecode(data.aws_secretsmanager_secret_version.mailing.secret_string)
mailing_secrets_list = [
for name, value in local.mailing_secrets : {
name = name
value = value
}
]
}
We are using the jsondecode
function to get a representation of the result as Terraform language values.
The next endeavor is creating a list of environment variables in a way that we benefit from.
Since AWS expects the environment variables to be passed as key/value pairs with name
- value
notation, we are using terraform's for
looping functionality to craft a list of objects.
Modelling the task definition with the help of templates
To make life easier and code cleaner, I've abstracted the task definition to a separate template file and stored it in definitions/template.json
.
The bit that should be interesting to the reader of this essay is the following. The rest of the definition is boilerplate code that can be found in the appropriate documentation.
[
{
"environment": ${jsonencode(environment)}
}
]
To use the template, we are relying on Terraform's templatefile
which renders the template for us and contains it in a single variable.
locals {
mailing_definition = templatefile("${path.module}/definitions/template.json", {
environment = local.mailing_secrets_list
})
}
Using the task definition in a task resource
The last step, although not strictly relevant to this article, is taking advantage of the task definition we created.
Using aws_ecs_task_definition
we can assign the value of the local.mailing_definition
to the container_definitions
attribute.
resource "aws_ecs_task_definition" "mailing" {
container_definitions = local.mailing_definition
}
The gist
The reason this article exists is that I didn't find a similar one. But, seldom is parthenogenesis in software, and this solution is no different.
These are the resources that led me to this solution.
https://github.com/hashicorp/terraform-provider-aws/issues/6503
Posted on December 10, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.