AWS ECR Push and Pull Private Image

Keywords: AWS ECR, Docker, Login, Auth, Authentication, Push, Pull, Private, Image

What’s the Problem?

AWS ECR 是一个私有的 Container Registry, 你当然是要鉴权才能够 Pull 和 Pull Container Image 了. AWS 支持用 Docker CLI 客户端来做这件事.

Overview

执行下面的命令获得一个 token 用于登录某个 aws account, 某个 aws region 下的 ecr:

aws ecr get-login --region <aws-region> --no-include-email --profile <aws-profile>

上面的命令会返回一个 token 字符串, 然后你就可以让你的 client login ECR 了:

docker login -u AWS -p <token-value> https://<aws-account-id>.dkr.ecr.<aws-region>.amazonaws.com.

其中 <token-value> 部分就是验证用的 token. 这个字符串就是登录命令, 可以直接拷贝到命令行中执行. 所以你甚至可以直接使用下面的命令执行它, 免去了复制粘贴的步骤:

$(aws ecr get-login --region <aws-region> --no-include-email --profile <aws-profile>)

根据 docker login 的文档 -p 命令是不安全的, 会将 token 暴漏在 cli 的命令历史记录中, 官方推荐使用 --password-stdin 选项传入 token. 所以最终的命令是这样的:

AWS_REGION="us-east-1"
AWS_PROFILE="my-aws-profile"
ecr_uri="https://111122223333.dkr.ecr.${AWS_REGION}.amazonaws.com"

aws ecr get-login --no-include-email --region ${AWS_REGION} --profile ${AWS_PROFILE} | awk '{printf $6}' | docker login -u AWS ${ecr_uri} --password-stdin

在这之后, docker pull 或是 docker push 就会有操作权限了.

Shell Script

这里我写了一个脚本, 可以非常方便地让 docker cli login 到 ECR. 脚本内容如下:

  1# -*- coding: utf-8 -*-
  2
  3"""
  4This shell script automates docker login to AWS ECR.
  5
  6Requirements:
  7
  8- Python3.7+
  9- `fire>=0.1.3,<1.0.0 <https://pypi.org/project/fire/>`_
 10
 11Usage:
 12
 13.. code-block:: bash
 14
 15    $ python ecr_login.py -h
 16"""
 17
 18import typing as T
 19import boto3
 20import base64
 21import subprocess
 22
 23import fire
 24
 25
 26def get_ecr_auth_token_v1(
 27    ecr_client,
 28    aws_account_id,
 29) -> str:
 30    """
 31    Get ECR auth token using boto3 SDK.
 32    """
 33    res = ecr_client.get_authorization_token(
 34        registryIds=[
 35            aws_account_id,
 36        ],
 37    )
 38    b64_token = res["authorizationData"][0]["authorizationToken"]
 39    user_pass = base64.b64decode(b64_token.encode("utf-8")).decode("utf-8")
 40    auth_token = user_pass.split(":")[1]
 41    return auth_token
 42
 43
 44def get_ecr_auth_token_v2(
 45    aws_region: str,
 46    aws_profile: T.Optional[str] = None,
 47):
 48    """
 49    Get ECR auth token using AWS CLI.
 50    """
 51    args = ["aws", "ecr", "get-login", "--region", aws_region, "--no-include-email"]
 52    if aws_profile is not None:
 53        args.extend(["--profile", aws_profile])
 54    response = subprocess.run(args, check=True, capture_output=True)
 55    text = response.stdout.decode("utf-8")
 56    auth_token = text.split(" ")[5]
 57    return auth_token
 58
 59
 60def docker_login(
 61    auth_token: str,
 62    registry_url: str,
 63) -> bool:
 64    """
 65    Login docker cli to AWS ECR.
 66
 67    :return: a boolean flag to indicate if the login is successful.
 68    """
 69    pipe = subprocess.Popen(["echo", auth_token], stdout=subprocess.PIPE)
 70    response = subprocess.run(
 71        ["docker", "login", "-u", "AWS", registry_url, "--password-stdin"],
 72        stdin=pipe.stdout,
 73        capture_output=True,
 74    )
 75    text = response.stdout.decode("utf-8")
 76    return "Login Succeeded" in text
 77
 78
 79def main(
 80    aws_profile: T.Optional[str] = None,
 81    aws_account_id: T.Optional[str] = None,
 82    aws_region: T.Optional[str] = None,
 83):
 84    """
 85    Login docker cli to AWS ECR using boto3 SDK and AWS CLI.
 86
 87    :param aws_profile: specify the AWS profile you want to use to login.
 88        usually this parameter is used on local laptop that having awscli
 89        installed and configured.
 90    :param aws_account_id: explicitly specify the AWS account id. if it is not
 91        given, it will use the sts.get_caller_identity() to get the account id.
 92        you can use this to get the auth token for cross account access.
 93    :param aws_region: explicitly specify the AWS region for boto3 session
 94        and ecr repo. usually you need to set this on EC2, ECS, Cloud9,
 95        CloudShell, Lambda, etc ...
 96    """
 97    boto_ses = boto3.session.Session(
 98        region_name=aws_region,
 99        profile_name=aws_profile,
100    )
101    ecr_client = boto_ses.client("ecr")
102    if aws_account_id is None:
103        sts_client = boto_ses.client("sts")
104        res = sts_client.get_caller_identity()
105        aws_account_id = res["Account"]
106
107    print("get ecr auth token ...")
108    auth_token = get_ecr_auth_token_v1(
109        ecr_client=ecr_client,
110        aws_account_id=aws_account_id,
111    )
112    if aws_region is None:
113        aws_region = boto_ses.region_name
114    print("docker login ...")
115    flag = docker_login(
116        auth_token=auth_token,
117        registry_url=f"https://{aws_account_id}.dkr.ecr.{aws_region}.amazonaws.com",
118    )
119    if flag:
120        print("login succeeded!")
121    else:
122        print("login failed!")
123
124
125def run():
126    fire.Fire(main)
127
128
129if __name__ == "__main__":
130    run()