The problem
I want to achieve following things:
- I want to use AWS ECR as my repository,
- the repositories must be private,
- in the
docker-compose.yml
I want to refer to the docker images using custom domain - not hardcoding to the AWS ECR, - as long as I’m using AWS ECR as the repository, I want to use ECR Credentials Helper (no
docker login
), - I want to use AWS Cognito to control what users can fetch what image from the repository and to block access for the particular user if needed.
High Level solution
- Install AWS ECR Credentials Helper Login,
- Configure Docker to use custom wrapper over AWS ECR Credentials Helper Login script to allow to use custom domains,
- Utilize
credentials_process
property of Credentials Helper to invoke a script that will authorize the user against Cognito.
That’s how it more or less looks like in a visualized way:
Detailed steps
Install AWS ECR Credentials Helper Login
That’s the easiest part. We need to have the AWS ECR. On Ubuntu it’s just a matter of installing it from APT:
sudo apt install amazon-ecr-credential-helper
Use custom wrapper over AWS ECR Credentials Helper
AWS ECR Wrapper
The downloaded AWS ECR Credentials Helper will not work with your custom domain (e.g. docker.mydomain.com
).
It will fail if the URL is not the one from AWS ECR (e.g. 123456789012.dkr.ecr.eu-central-1.amazonaws.com
).
That’s due to this part.
Solution is to use this awesome wrapper over ECR credentials helper authored by amencevice you can find here.
It will create a mapping of your custom domain to the ECR URL. That’s not perfect as I wanted to keep
all pieces of information related to AWS ECR apart from the device where I run docker (and hide it behind custom domain;
only proxy should be aware of it) but that’s much better than referencing AWS ECR directly in docker-compose.yml
.
The mapping by default is put into ~/.ecr/custom.json and will be read by the wrapper. Exemplary content of the mapping is:
{
"docker.mydomain.com": "123456789012.dkr.ecr.eu-central-1.amazonaws.com"
}
AWS ECR Wrapper cred helper configuration
Now we need to inform Docker to use this new wrapper. See the docs in linked amencevice GitHub account, but in short:
- put the
docker-credential-ecr-custom
somewhere on$PATH
- refer it from the
~/.docker/config.json
credHelpers
:
{
"credHelpers": {
"docker.mydomain.com": "ecr-custom"
}
}
Note: the script named docker-credential-foo-bar
should be referenced from config.json
as foo-bar
.
At this point the Docker will check what credentials helper it should use for docker.mydomain.com
and will use the AWS
ECR Wrapper script, that will use the mapping from ~/.ecr/custom.json
to get the correct ECR URL and pass the rest of
the load to the original AWS ECR Credentials Helper.
Use custom credentials_process to login using AWS Cognito
Now we need to bootstrap our AWS Cognito script to authorize the user and get the credentials to access AWS ECR.
We do this by using credential_process
property of the
ECR (docs).
We do this by putting credentials_process
entry in ~/.aws/config
:
[default]
credential_process = /var/piotr/cognito-ecr-credentials-provider.sh
The cognito-ecr-credentials-provider.sh
must return particular response to stdin
after making sure the user is correctly
authenticated (and authorized to access the particular ECR image). What you could use is something like this:
#!/bin/bash
# Required env-vars (to be provided by execution environment of this script) are:
# AWS_USERNAME
# AWS_PASSWORD
# AWS_REGION
# AWS_ACCOUNT_ID
# AWS_COGNITO_CLIENT_ID
# AWS_COGNITO_USER_POOL_ID
# AWS_COGNITO_IDENTITY_POOL_ID
INITIATE_AUTH_RESPONSE=$(curl -sX POST --location "https://cognito-idp.$AWS_REGION.amazonaws.com" \
-H "Content-Type: application/x-amz-json-1.1" \
-H "X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth" \
-d "{
\"AuthFlow\" : \"USER_PASSWORD_AUTH\",
\"ClientId\" : \"$AWS_COGNITO_CLIENT_ID\",
\"AuthParameters\" : {
\"USERNAME\" : \"$AWS_USERNAME\",
\"PASSWORD\" : \"$AWS_PASSWORD\"
}
}")
ID_TOKEN=$(echo "$INITIATE_AUTH_RESPONSE" | jq -r '.AuthenticationResult.IdToken')
GET_ID_RESPONSE=$(curl -sX POST --location "https://cognito-identity.$AWS_REGION.amazonaws.com" \
-H "Content-Type: application/x-amz-json-1.1" \
-H "X-Amz-Target: com.amazonaws.cognito.identity.model.AWSCognitoIdentityService.GetId" \
-d "{
\"AccountId\": \"$AWS_ACCOUNT_ID\",
\"IdentityPoolId\": \"$AWS_COGNITO_IDENTITY_POOL_ID\",
\"Logins\": {
\"cognito-idp.$AWS_REGION.amazonaws.com/$AWS_COGNITO_USER_POOL_ID\" : \"$ID_TOKEN\"
}
}")
IDENTITY_ID=$(echo "$GET_ID_RESPONSE" | jq -r '.IdentityId')
GET_CREDENTIALS_RESPONSE=$(curl -sX POST --location "https://cognito-identity.$AWS_REGION.amazonaws.com" \
-H "Content-Type: application/x-amz-json-1.1" \
-H "X-Amz-Target: com.amazonaws.cognito.identity.model.AWSCognitoIdentityService.GetCredentialsForIdentity" \
-d "{
\"IdentityId\": \"$IDENTITY_ID\",
\"Logins\": {
\"cognito-idp.$AWS_REGION.amazonaws.com/$AWS_COGNITO_USER_POOL_ID\": \"$ID_TOKEN\"
}
}")
EXPIRATION_IN_UNIX_TIMESTAMP=$(echo "$GET_CREDENTIALS_RESPONSE" | jq -r '.Credentials.Expiration')
EXPIRATION_IN_ISO8601=$(date -d "@$EXPIRATION_IN_UNIX_TIMESTAMP" -u +"%Y-%m-%dT%H:%M:%SZ")
AWS_CREDENTIALS_FORMAT=$(echo "$GET_CREDENTIALS_RESPONSE" | jq "
{
\"Version\": 1,
\"AccessKeyId\": .Credentials.AccessKeyId,
\"SecretAccessKey\": .Credentials.SecretKey,
\"SessionToken\": .Credentials.SessionToken,
\"Expiration\": \"$EXPIRATION_IN_ISO8601\"
}")
printf "%s" "$AWS_CREDENTIALS_FORMAT"
This script utilizes a bunch of env-vars and proceeds with the login procedure against AWS Cognito for a user that is in given Cognito User Pool.
In this particular case, the following app client configuration (authentication flows) are enabled:
- ALLOW_USER_PASSWORD_AUTH
- ALLOW_REFRESH_TOKEN_AUTH
The end
The returned credentials will be stored in the credentials’ storage.
Now, in my eyes that’s a LOT of work for something that should not be that complicated…
- https://github.com/aws/containers-roadmap/issues/299
- https://github.com/awslabs/amazon-ecr-credential-helper
- https://github.com/amancevice/terraform-aws-custom-ecr-domain
- https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-sourcing-external.html
- https://github.com/awslabs/amazon-ecr-credential-helper
- https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html
- https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetId.html
- https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetCredentialsForIdentity.html