Using Secrets Manager environment variables in AWS ECS

A really quick 3-step demonstration on how to use AWS Secrets Manager to pass sensitive data as environment variables to containers running in the managed Elastic Container Service (ECS).

In this article it is assumed that you have a basic knowledge on how to create AWS ECS services on an existing cluster. We will use the secure-config-test project with a freely available image on Docker Hub to set-up an ECS service. This small app starts up a simple http server returning a secret configuration item managed by the secure-config Node.js library. To run properly, the secret config decryption key needs to be passed as an environment variable named CONFIG_ENCRYPTION_KEY to the container. We’ll use AWS Secrets Manager to handle this key in a secure way instead of passing it as a plain text variable in the ECS service definition.

Create a secret in Secrets Manager

First, let’s create a managed secret in AWS Secrets Manager. In the AWS console, head over to Secrets Manager. Click on Store a new secret in the upper right corner and create a secret with the following parameters:

  • Type: Other type of secret
  • Key: CONFIG_ENCRYPTION_KEY
  • Value: 00000000000000000000000000000000
  • Encryption key: own KMS key or leave the default aws/secretsmanager
  • Name: secureConfig/encryptionKey
  • Rotation: off

After the creation of the secret is complete, it should look like this. You can already copy the ARN for the next step.

aws-secrets-manager-encryptionkey-secret-edited

Add permissions to the ECS execution role in IAM

Once the secret is created, we’ll need to enable Elastic Container Service to access it. To do so, the IAM role that is used to run the ECS service task must have sufficient permissions. To enable the role, ecsTaskExecutionRole in our case, edit it in IAM and add a custom inline policy allowing the secretsmanager:GetSecretValue action on the created secret.

The inline policy needed to access the secret is the following – replace the secrets ARN with yours.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue"
      ],
      "Resource": [
        "arn:aws:secretsmanager:eu-central-1:xxxx:secret:secureConfig/encryptionKey-Zc6y5k"
      ]
    }
  ]
}

Note: You could also add a policy allowing the ECS execution role to read any secret out of Secrets Manager but as a best practice you should narrow the privileges down to the needed secrets by specifying their ARN’s in the policy.

Giving the inline policy a descriptive name like Read-SecretsManager-secrets, the final role permissions should look like that.

aws-secrets-manager-ecs-task-execution-role-edited

Using the secret in the ECS task definition

Next, head over to ECS in the AWS console and create a task definition as you are used to. The basic parameters are:

  • Container Image: tsmx/secure-config-test
  • Port Mapping: 3000 to 3000, http
  • Task execution role: the IAM role edited earlier for Secrets Manager access, ecsTaskExecutionRole in our example

To pass a secret as an environment variable, add a secrets property to the task definition as a sibling to the desired containerDefinitions array element. There, add an array element with name CONFIG_ENCRYPTION_KEY as it is expected by the container image. For valueFrom provide the secrets ARN followed by a “:” and the name of the key to be retrieved out of the secret plus a trailing “::”. This will extract exactly this one specific key of the stored secret.

aws-secrets-manager-ecs-task-definition-edited

At the time of writing this article, adding secrets was only possible by editing the task definition JSON directly. The final JSON should look like this.

{
  "taskDefinitionArn": "arn:aws:ecs:eu-central-1:xxxx:task-definition/secure-config-test:4",
  "containerDefinitions": [
    {
      "name": "secure-config",
      "image": "tsmx/secure-config-test",
      "cpu": 0,
      "portMappings": [
        {
          "name": "secure-config-3000-tcp",
          "containerPort": 3000,
          "hostPort": 3000,
          "protocol": "tcp",
          "appProtocol": "http"
        }
      ],
      "essential": true,
      "environment": [],
      "mountPoints": [],
      "volumesFrom": [],
      "secrets": [
        {
          "name": "CONFIG_ENCRYPTION_KEY",
          "valueFrom": "arn:aws:secretsmanager:eu-central-1:xxxx:secret:secureConfig/encryptionKey-Zc6y5k:CONFIG_ENCRYPTION_KEY::"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/secure-config-test",
          "awslogs-create-group": "true",
          "awslogs-region": "eu-central-1",
          "awslogs-stream-prefix": "ecs"
        }
      },
      "systemControls": []
    }
  ],
  "family": "secure-config-test",
  "executionRoleArn": "arn:aws:iam::xxxx:role/ecsTaskExecutionRole",
  "networkMode": "awsvpc",
  "revision": 4,
  "volumes": [],
  "status": "ACTIVE",
  "requiresAttributes": [
    {
      "name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
    },
    {
      "name": "ecs.capability.execution-role-awslogs"
    },
    {
      "name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
    },
    {
      "name": "ecs.capability.secrets.asm.environment-variables"
    },
    {
      "name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
    },
    {
      "name": "ecs.capability.task-eni"
    },
    {
      "name": "com.amazonaws.ecs.capability.docker-remote-api.1.29"
    }
  ],
  "placementConstraints": [],
  "compatibilities": [
    "EC2",
    "FARGATE"
  ],
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "cpu": "1024",
  "memory": "3072",
  "runtimePlatform": {
    "cpuArchitecture": "X86_64",
    "operatingSystemFamily": "LINUX"
  },
  "registeredAt": "2024-12-08T20:35:48.186Z",
  "registeredBy": "arn:aws:iam::xxxx:user/admin",
  "tags": []
}

Deploy the ECS service and test

After the task definition is done, deploy a service for it by clicking Deploy --> Create service in the task. Make sure it is deployed with public access for testing purposes…

  • Choose a public subnet in the desired VPC
  • Assign a security group allowing access to port 3000 from anywhere

After a few minutes the new service should be ready and you can go to your ECS Cluster --> services --> your service --> tasks --> running task --> network bindings to find out the External link (should be a public IP on port 3000).

Opening this link you should see the following in your browser…

aws-secrets-manager-ecs-test

Congratulations! This indicates the secret encryption key has been successfully injected to the ECS service as an environment variable by using AWS Secrets Manager.

Useful links