AWS Fargate Secrets
10 Dec 2020
If you want to inject a password (or other secret) as an env var into a Docker container in AWS Fargate, here's how. (Note that the make-believe AWS account number 999999999999 is used throughout.)
First, let's create a Docker image whose only job it is to print its env vars, to prove that we have figured out how to set the env vars we want to set.
Here is the Dockerfile (literally named Dockerfile
:
FROM alpine CMD ["env"]
Here's how we build and run the docker image:
$ docker build --tag envprinter:1.0 . $ docker container run --rm -e FOO envprinter:1.0 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOSTNAME=d02bb7e3585d FOO=bar HOME=/root
Let's create an ECR repository to hold this custom docker image that will run in Fargate. (You'll need IAM: AmazonEC2ContainerRegistryFullAccess)
$ aws ecr create-repository \ --repository-name environmentprinter $ aws --output text ecr describe-repositories \ --query 'repositories[*].[repositoryUri]' 999999999999.dkr.ecr.us-east-1.amazonaws.com/environmentprinter
Time to tag our Docker image and push it to our new repository.
$ docker tag \ envprinter:1.0 \ 999999999999.dkr.ecr.us-east-1.amazonaws.com/environmentprinter:1.0 $ aws ecr get-login-password \ | docker login \ --username AWS \ --password-stdin \ https://999999999999.dkr.ecr.us-east-1.amazonaws.com $ docker push \ 999999999999.dkr.ecr.us-east-1.amazonaws.com/environmentprinter:1.0
Let's create a secret that we will store in Systems Manager / Parameter Store. (You'll need IAM: AmazonSSMFullAccess)
$ aws ssm put-parameter \ --name MY_PASSWORD \ --value PoorlyChosen \ --type SecureString
Let's create an ECR cluster to run our Docker image as a Fargate job in. (You'll need IAM: AmazonECS_FullAccess)
$ aws ecs create-cluster \ --cluster-name foo
Let's create a task execution role that our Farget task will run as.
$ cat printenvironmentRole_trust_policy.json { "Version": "2008-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Principal": { "Service": "ecs-tasks.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } $ aws iam create-role \ --role-name printenvironmentRole \ --assume-role-policy-document file://printenvironmentRole_trust_policy.json
Let's create a managed policy named read-my-password that will allow reading MY_PASSWORD from Systems Manager / Parameter Store.
$ aws --output text ssm get-parameter \ --name MY_PASSWORD \ --query 'Parameter.[ARN]' arn:aws:ssm:us-east-1:999999999999:parameter/MY_PASSWORD $ cat read-my-password-policy-document.json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ssm:GetParameters", "secretsmanager:GetSecretValue", "kms:Decrypt" ], "Resource": [ "arn:aws:ssm:us-east-1:999999999999:parameter/MY_PASSWORD" ] } ] } $ aws iam create-policy \ --policy-name read-my-password \ --policy-document file://read-my-password-policy-document.json
Let's attach read-my-password managed policy to printenvironmentRole
$ aws --output text iam list-policies \ --scope Local \ --query 'Policies[*].[Arn]' arn:aws:iam::994899087112:policy/read-my-password $ aws iam attach-role-policy \ --role-name printenvironmentRole \ --policy-arn arn:aws:iam::999999999999:policy/read-my-password
We also need to attach AmazonECSTaskExecutionRolePolicy managed policy to printenvironmentRole, or else our Fargate task won't have the permissions to run.
$ aws --output text iam list-policies \ --scope AWS\ --query 'Policies[?ends_with(Arn, `AmazonECSTaskExecutionRolePolicy`)]|[*][Arn]' arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy $ aws iam attach-role-policy \ --role-name printenvironmentRole \ --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
We need to check the logs of our Fargate task after it is finished, so let's create a CloudWatch log group for that.
$ aws logs create-log-group \ --log-group-name /ecs/printenvironment
Register a task definition named printenvironment
$ cat printenvironment-container-definitions.json [ { "name": "envprintercontainer", "image": "999999999999.dkr.ecr.us-east-1.amazonaws.com/environmentprinter:1.0", "essential": true, "secrets": [ { "valueFrom": "arn:aws:ssm:us-east-1:999999999999:parameter/MY_PASSWORD", "name": "MY_PASSWORD" } ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/printenvironment", "awslogs-region": "us-east-1", "awslogs-stream-prefix": "ecs" } } } ] $ aws --output text iam list-roles \ --query 'Roles[?RoleName==`printenvironmentRole`]|[*][Arn]' arn:aws:iam::994899087112:role/printenvironmentRole $ aws ecs register-task-definition \ --family printenvironment \ --execution-role-arn arn:aws:iam::999999999999:role/printenvironmentRole \ --container-definitions file://printenvironment-container-definitions.json \ --network-mode awsvpc \ --requires-compatibilities FARGATE \ --cpu 256 \ --memory 512
List available subnets and security groups so we can pick which ones we want to run our task with:
$ aws --output text ec2 describe-subnets \ --query 'Subnets[*].[AvailabilityZone,SubnetId,VpcId,CidrBlock]' us-east-1a subnet-09a50bcae65469211 vpc-04bdba9cb846c9f97 10.0.0.0/24 $ aws --output json ec2 describe-security-groups ### (returns blob of json too large to show)
Manually kick off the task to be sure it works.
$ cat run-task-network-configuration.json { "awsvpcConfiguration": { "subnets": [ "subnet-09a50bcae65469211" ], "securityGroups": [ "sg-03816181f609da88b" ], "assignPublicIp": "ENABLED" } } $ aws ecs run-task \ --cluster foo \ --task-definition printenvironment:1 \ --launch-type FARGATE \ --network-configuration file://run-task-network-configuration.json
Check the task's logs to see that the env vars were printed.
$ aws --output text logs describe-log-streams \ --log-group-name /ecs/printenvironment \ --order-by LastEventTime \ --descending \ --max-items 5 \ --query 'logStreams[*].[creationTime,logStreamName]' 1607623756054 ecs/envprintercontainer/09f914e84e274c6c92ad25e97e869622 $ aws --output text logs get-log-events \ --log-group-name /ecs/printenvironment \ --log-stream-name ecs/envprintercontainer/09f914e84e274c6c92ad25e97e869622 \ --query 'events[*].[timestamp,message]' 1607623756859 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 1607623756859 HOSTNAME=ip-10-0-0-79.ec2.internal 1607623756859 ECS_CONTAINER_METADATA_URI_V4=http://169.254.170.2/v4/96293374-3fd1-4cfa-a3d2-8498bf4b545e 1607623756859 MY_PASSWORD=PoorlyChosenPassword 1607623756859 AWS_DEFAULT_REGION=us-east-1 1607623756859 AWS_EXECUTION_ENV=AWS_ECS_FARGATE 1607623756859 AWS_REGION=us-east-1 1607623756859 ECS_CONTAINER_METADATA_URI=http://169.254.170.2/v3/96293374-3fd1-4cfa-a3d2-8498bf4b545e 1607623756859 HOME=/root
And Lo and Behold, there is MY_PASSWORD, correctly injected into the Fargate container!
FOOTNOTES
These links were very handy in helping me figure this out:
- https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data-parameters.html
- https://docs.aws.amazon.com/cli/latest/userguide/cli-usage-output.html#cli-usage-output-filter
- https://opensourceconnections.com/blog/2015/07/27/advanced-aws-cli-jmespath-query/