I tell you a secret: Provide Database credentials to an ECS Fargate task in AWS CDK

I tell you a secret: Provide Database credentials to an ECS Fargate task in AWS CDK

Originally published at dev.to on July 24, 2020.

This is my first post after thinking too much about what to write for a decade or so. ;) So let's overcome this blockade by talking about this delicate topic.

Motivation: Credentials aren’t safe in environment variables

Developers without a clue would come up with environment variables to store and pass those credentials. The nature of environment variables is, that they mostly are used to store readable text in them and make them accessible easily. Let’s imagine you would pass your production credentials to your database around in clear-text. If an attacker gains access to the instance itself or your container orchestrator, you are screwed up. In a nutshell: Don’t do it.

Consequently, we need to have an encrypted mechanism to store and to hand-over these credentials to our Fargate tasks as well as to our AWS persistence layer.

Prerequisites: AWS Services for storing secret information

AWS itself offers already two products to take care of the management of sensitive data (called secrets):

I don’t want to dive deeper into a comparison of both. I’d like to give the Secret-Manager a shot in this article, but you can exchange it without much hassle. The Secret Manager offers a key-rotation mechanism, which automatically can change the key after a certain period.

Both enable us to only retrieve and decrypt the secret information by eligible IAM roles.

Create two secrets in the AWS Secret Manager

To create a secret (key/value pair) for your specific database access, you could do it via the AWS Console or by the AWS CLI.

Important: Don’t create a JSON value for the secret key. The retrieval of it isn’t working out with Fargate.

Due to the special case related to Fargate, we need to create two secrets. One is for the username and the other for the password itself. Go ahead and create those:

aws secretsmanager create-secret \ 
--name prod/service/db/user \
--secret-string yourAwesomeUseraws secretsmanager create-secret \
--name prod/service/db/password \
--secret-string yourAwesomePassword

You’ll get an ARN back per secret. Remember both ARNs. Otherwise, if you forget about it, you can look them up later again.

Wire the secrets in our Infrastructure as Code / AWS CDK

We successfully created both secrets in the Secret Manager.

As introduced in the header, we now want to integrate both secrets in the AWS CDK code in order to ship them to our ECS Fargate task.

A few words on AWS CDK: AWS CDK enables you to write your AWS infrastructure in your favorite programming language. No YAML/JSON headaches, but instead testable, clean, and reusable code. Yes, I was also impressed when I first used it in 2018 for PoCs after it was in the developer preview. Its popularity and feature set has grown tremendously over the past two years. It’s even possible since a few days to integrate it with Terraform.. :O

Prerequisites: AWS CDK infrastructure

Alright, I assume that you are kind of familiar with it. Therefore, I highlight the gist instead.

I don't cover the following in AWS CDK:

  • the ECS cluster. The adaption of the Fargate task itself is important and will be covered in this article!
  • the VPC stack.
  • the wiring of the stacks in the main application (located in /bin).

The database adaption topic will be provided with an RDS example.

Import the secrets

We created both secrets outside of AWS CDK, so we have to import them as resources.

I highly recommend creating a separate Stack for that, which makes it essential for reusing them not only for the Fargate task but also for the (RDS) database.

Get both ARNs of the username and password in order to import them properly. This can be done like that:

One-third of the work is already done. Congrats! ;)

Passing the secrets to your AWS database

In the example code, we going to use the RDS Service from AWS. The good thing about is, that we can use the password secret natively. The username needs to be converted to a string before passing it to RDS.

That concludes the setup of the database with our secret credentials.

Passing the secrets to the ECS Fargate task

Here we go with the most precious piece of this article.

Basically, we apply the same principle as with the RDS-Stack. We provide the database credentials as stack-props. Additionally, we add the database connection information to the props, e.g. host, port, etc.

The taskRole is created to grant access to both secrets and has a default behaviour in this example. Under the hood, the command applies an IAM policy to the role. The read access is super important. Otherwise, the Fargate task can't read the actual content of both secrets during its runtime. This role needs to be passed to the ECS task by the argument taskRole.

The important part comes straight after the taskDef when appending the container. Spot the secrets section. Here they go!

As you can see, the secrets can live beside the regular environment variables. Feels decent like in a shared flat with beer and good music each evening, but with separated rooms! ;)

If you made it up here, then big thanks for staying! Feel free to contact me for further discussions. Additionally, I like to get feedback on my very first article! Cheers!

Michael