diff --git a/Dockerfile b/Dockerfile index 74fec3d..34f33a7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM nginx:1.12.0-alpine +FROM nginx:1.17.7-alpine RUN apk -v --update add \ python \ @@ -8,12 +8,9 @@ RUN apk -v --update add \ apk -v --purge del py-pip && \ rm /var/cache/apk/* -ADD configs/nginx/nginx.conf /etc/nginx/nginx.conf ADD configs/nginx/ssl /etc/nginx/ssl - -ADD configs/entrypoint.sh /entrypoint.sh -ADD configs/auth_update.sh /auth_update.sh -ADD configs/renew_token.sh /renew_token.sh +ADD configs/nginx/*.conf /etc/nginx/ +ADD configs/*.sh / EXPOSE 80 443 diff --git a/README.md b/README.md index f3fbc45..70f5282 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,25 @@ Based on official nginx alpine. [Docker image repository](https://hub.docker.com/r/catalinpan/aws-ecr-proxy/) -The container will renew the aws token every 6 hours. +The container will renew the AWS token every 6 hours. -Variables: -``` -AWS_KEY -AWS_SECRET -REGION -RENEW_TOKEN - default 6h -REGISTRY_ID - optional, used for cross account access -``` +### Variables + +The following table describes the parameters you can provide as Docker environment variables. -### Health check +| Name | Default value | Description | +|---------------------------|---------------|-----------------------------------------------------------------| +| `AWS_KEY` | | The AWS access key used to execute AWS ECR API requests. | +| `AWS_SECRET` | | The AWS secret used to execute AWS ECR API requests. | +| `DOCKER_REGISTRY_VERSION` | 2 | The version of the Docker registry to use. | +| `REGION` | | The AWS region where your AWS ECR registries are located. | +| `RENEW_TOKEN` | 6h | The interval used to indicate how often to renew the AWS token. | +| `REGISTRY_ID` | | Used for cross account access. | -To check the health of the container/registry use ```FQDN/ping``` which will give you the heath of the registry with the correct status code. + +### Health check + +To check the health of the container/registry use `FQDN/ping` which will give you the heath of the registry with the correct status code. ### AWS instance with IAM role @@ -31,9 +36,11 @@ The configs will be checked in the following order: - variables declared at run time - IAM role -If none are found the container will not start. Check the logs with ```docker logs CONTAINER_ID``` +If none are found the container will not start. Check the logs with `docker logs CONTAINER_ID`. + +## Docker -## Docker run: +### Run ##### Without ssl This will require either to add insecure registry URL or a load balancer with valid ssl certificates. Check https://docs.docker.com/registry/insecure/ for more details. @@ -53,33 +60,60 @@ docker run -e AWS_SECRET='YOUR_AWS_SECRET' \ -d catalinpan/aws-ecr-proxy ``` ##### With a valid AWS CLI configuration file -The configuration should look like below example. +The configuration should look like below example. ``` cat ~/.aws/config ``` ``` [default] # region example eu-west-1 -region = REGION +region = REGION aws_access_key_id = YOUR_AWS_KEY aws_secret_access_key = YOUR_AWS_SECRET ``` ``` -docker run -v ~/.aws:/root/.aws:ro --v `pwd`/YOUR_CERTIFICATE.key:/etc/nginx/ssl/default.key:ro \ --v `pwd`/YOUR_CERTIFICATE.crt:/etc/nginx/ssl/default.crt:ro \ --d catalinpan/aws-ecr-proxy +docker run -v ~/.aws:/root/.aws:ro \ + -v `pwd`/YOUR_CERTIFICATE.key:/etc/nginx/ssl/default.key:ro \ + -v `pwd`/YOUR_CERTIFICATE.crt:/etc/nginx/ssl/default.crt:ro \ + -d catalinpan/aws-ecr-proxy ``` ##### IAM role configured -with region and credentials from IAM role +With region and credentials from IAM role. ``` docker run -d catalinpan/aws-ecr-proxy ``` -with region as environment variable and credentials from IAM role +With region as environment variable and credentials from IAM role. ``` docker run -e REGION='YOUR_AWS_REGION' -d catalinpan/aws-ecr-proxy ``` +##### With an explicit Docker registry version +``` +docker run -d catalinpan/aws-ecr-proxy \ + -e DOCKER_REGISTRY_VERSION=1 +``` + +### Build +Build the default `catalinpan/aws-ecr-proxy` image with the version specified in the `version.txt` file. +``` +./build.sh +``` +Build an image with a custom name and version. +``` +./build.sh --name=company-name/aws-ecr-proxy --version=1.0.0 +``` + +### Publish +Publish the default `catalinpan/aws-ecr-proxy` image with the version specified in the `version.txt` file. +``` +./publish.sh --latest +``` +Publish an image with a custom name and version. +``` +./publish.sh --latest --name=company-name/aws-ecr-proxy --version=1.0.0 +``` + + ## SSL The certificates included are just to get nginx started. Generate your own certificate, get valid ssl certificates or use the container behind a load balancer with valid SSL certificates. @@ -97,4 +131,4 @@ The configs can be changed to get aws_config and ssl certificates as secrets. The configuration provided will require valid ssl certificates or to be behind a load balancer with valid ssl. #### DaemonSet -The daemonSet will be available on all the nodes. Deployments can use ```127.0.0.1:5000/container_name:tag``` instead of ```FQDN/container_name:tag``` +The daemonSet will be available on all the nodes. Deployments can use `127.0.0.1:5000/container_name:tag` instead of `FQDN/container_name:tag`. diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..1f5ac43 --- /dev/null +++ b/build.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +usage () { + printf "NAME\n" + printf " build.sh -- build a new Docker image.\n" + printf "SYNOPSIS\n" + printf " build.sh [options]\n" + printf "DESCRIPTION\n" + printf " build.sh is a tool to build a new Docker image.\n" + printf "OPTIONS\n" + printf " -h, --help\n" + printf " Usage help. This lists all current command line options with a short description.\n" + printf " --name=\n" + printf " The name of the Docker image to build, default to 'catalinpan/aws-ecr-proxy'.\n" + printf " --version\n" + printf " The version of the Docker image to build, default to the content of 'version.txt'.\n" +} + +NAME=catalinpan/aws-ecr-proxy +VERSION=$(cat version.txt) + +while [ $# -gt 0 ] +do + case $1 in + -h | --help) + usage + exit + ;; + --name=*) + NAME=${1#*=} + ;; + --version=*) + VERSION=${1#*=} + ;; + *) + usage + exit + ;; + esac + shift +done + +docker build --no-cache --pull -t "${NAME}:${VERSION}" . diff --git a/configs/auth_update.sh b/configs/auth_update.sh index f54e76c..548960a 100755 --- a/configs/auth_update.sh +++ b/configs/auth_update.sh @@ -8,8 +8,8 @@ if [ "$REGISTRY_ID" = "" ] then token=$(aws ecr get-login --no-include-email | awk '{print $6}') else - token=$(aws ecr get-login --no-include-email --registry-ids $REGISTRY_ID | awk '{print $6}') + token=$(aws ecr get-login --no-include-email --registry-ids "${REGISTRY_ID}" | awk '{print $6}') fi -auth_n=$(echo AWS:${token} | base64 |tr -d "[:space:]") +auth_n=$(echo "AWS:${token}" | base64 |tr -d "[:space:]") sed -i "s|${auth%??}|${auth_n}|g" ${nx_conf} diff --git a/configs/entrypoint.sh b/configs/entrypoint.sh index 4dd0c42..2eb7f5a 100755 --- a/configs/entrypoint.sh +++ b/configs/entrypoint.sh @@ -10,7 +10,7 @@ header_config() { echo "[default]" > /root/.aws/config } region_config() { - echo "region = $@" >> /root/.aws/config + echo "region = $*" >> /root/.aws/config } test_iam() { @@ -18,7 +18,7 @@ test_iam() { } test_config() { - grep -qrni $@ ${AWS_FOLDER} + grep -qrni "$@" ${AWS_FOLDER} } fix_perm() { @@ -30,10 +30,10 @@ if test_config region then echo "region found in ~/.aws mounted as secret" # configure regions if variable specified at run time -elif [[ "$REGION" != "" ]] +elif [ "$REGION" != "" ] then header_config - region_config $REGION + region_config "${REGION}" fix_perm # check if the region can be pulled from AWS IAM elif test_iam @@ -41,7 +41,7 @@ then echo "region detected from iam" REGION=$(wget -q -O- ${AWS_IAM} | grep 'region' |cut -d'"' -f4) header_config - region_config $REGION + region_config "${REGION}" fix_perm else echo "No region detected" @@ -53,7 +53,7 @@ if test_config aws_access_key_id then echo "aws key and secret found in ~/.aws mounted as secrets" # if both key and secret are declared -elif [[ "$AWS_KEY" != "" && "$AWS_SECRET" != "" ]] +elif [ "$AWS_KEY" != "" ] && [ "$AWS_SECRET" != "" ] then echo "aws_access_key_id = $AWS_KEY aws_secret_access_key = $AWS_SECRET" >> ${AWS_FOLDER}/config @@ -70,20 +70,39 @@ else fi fi +# Check the Docker registry version to use +if [ -z "${DOCKER_REGISTRY_VERSION}" ] +then + DOCKER_REGISTRY_VERSION=2 +elif [ "${DOCKER_REGISTRY_VERSION}" != "1" ] && [ "${DOCKER_REGISTRY_VERSION}" != "2" ] +then + echo "Docker registry version ${DOCKER_REGISTRY_VERSION} is invalid !" + exit 1 +fi + +# Pick the right NGinx configuration file +echo "Docker registry version is ${DOCKER_REGISTRY_VERSION}" +cat /etc/nginx/nginx-docker-registry-v${DOCKER_REGISTRY_VERSION}.conf > ${nx_conf} + # update the auth token if [ "$REGISTRY_ID" = "" ] -then +then aws_cli_exec=$(aws ecr get-login --no-include-email) else - aws_cli_exec=$(aws ecr get-login --no-include-email --registry-ids $REGISTRY_ID) + aws_cli_exec=$(aws ecr get-login --no-include-email --registry-ids "${REGISTRY_ID}") fi auth=$(grep X-Forwarded-User ${nx_conf} | awk '{print $4}'| uniq|tr -d "\n\r") token=$(echo "${aws_cli_exec}" | awk '{print $6}') -auth_n=$(echo AWS:${token} | base64 |tr -d "[:space:]") +auth_n=$(echo "AWS:${token}" | base64 |tr -d "[:space:]") reg_url=$(echo "${aws_cli_exec}" | awk '{print $7}') -sed -i "s|${auth%??}|${auth_n}|g" ${nx_conf} -sed -i "s|REGISTRY_URL|$reg_url|g" ${nx_conf} +# We use a '.new' file to prevent errors like this one. +# 'sed: can't move '/etc/nginx/nginx.confhgFhDa' to '/etc/nginx/nginx.conf': Resource busy' +cp ${nx_conf} ${nx_conf}.new +sed -i "s|${auth%??}|${auth_n}|g" ${nx_conf}.new +sed -i "s|REGISTRY_URL|$reg_url|g" ${nx_conf}.new +echo "" > ${nx_conf} +cat ${nx_conf}.new > ${nx_conf} /renew_token.sh & diff --git a/configs/nginx/nginx.conf b/configs/nginx/nginx-docker-registry-v1.conf similarity index 100% rename from configs/nginx/nginx.conf rename to configs/nginx/nginx-docker-registry-v1.conf diff --git a/configs/nginx/nginx-docker-registry-v2.conf b/configs/nginx/nginx-docker-registry-v2.conf new file mode 100644 index 0000000..1c85614 --- /dev/null +++ b/configs/nginx/nginx-docker-registry-v2.conf @@ -0,0 +1,60 @@ +events { + worker_connections 4096; +} + +http { + resolver 8.8.8.8 8.8.4.4; + + server { + + listen 80 default_server; + server_name _; + + location /v2/ { + set $upstream REGISTRY_URL; + + proxy_pass $upstream; + proxy_redirect $upstream http://$host; + + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-User "Basic $http_authorization"; + proxy_set_header Authorization "Basic $http_authorization"; + + proxy_pass_header Server; + + client_max_body_size 0; + proxy_connect_timeout 300s; + proxy_read_timeout 300s; + proxy_send_timeout 300s; + send_timeout 300s; + } + } + + server { + listen 443 ssl; + server_name _; + ssl_certificate /etc/nginx/ssl/default.crt; + ssl_certificate_key /etc/nginx/ssl/default.key; + + location /v2/ { + set $upstream REGISTRY_URL; + + proxy_pass $upstream; + proxy_redirect $upstream https://$host; + + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-User "Basic $http_authorization"; + proxy_set_header Authorization "Basic $http_authorization"; + + proxy_pass_header Server; + + client_max_body_size 0; + proxy_connect_timeout 300s; + proxy_read_timeout 300s; + proxy_send_timeout 300s; + send_timeout 300s; + } + } +} diff --git a/configs/renew_token.sh b/configs/renew_token.sh index 598d541..d6c5e94 100755 --- a/configs/renew_token.sh +++ b/configs/renew_token.sh @@ -1,6 +1,6 @@ #!/bin/sh -while sleep ${RENEW_TOKEN:-6h} +while sleep "${RENEW_TOKEN:-6h}" do /auth_update.sh nginx -s reload diff --git a/publish.sh b/publish.sh new file mode 100755 index 0000000..189c762 --- /dev/null +++ b/publish.sh @@ -0,0 +1,67 @@ +#!/bin/sh + +set -e + +usage () { + printf "NAME\n" + printf " publish.sh -- push Docker image to registry.\n" + printf "SYNOPSIS\n" + printf " publish.sh [options]\n" + printf "DESCRIPTION\n" + printf " publish.sh is a tool for pushing the Docker image to registry.\n" + printf "OPTIONS\n" + printf " -h, --help\n" + printf " Usage help. This lists all current command line options with a short description.\n" + printf " --latest\n" + printf " Also push/update the latest tag.\n" + printf " --name=\n" + printf " The name of the Docker image to publish, default to 'catalinpan/aws-ecr-proxy'.\n" + printf " --version\n" + printf " The version of the Docker image to publish, default to the content of 'version.txt'.\n" +} + +NAME=catalinpan/aws-ecr-proxy +VERSION=$(cat version.txt) + +while [ $# -gt 0 ] +do + case $1 in + -h | --help) + usage + exit + ;; + --latest) + PUSH_LATEST=1 + ;; + --name=*) + NAME=${1#*=} + ;; + --version=*) + VERSION=${1#*=} + ;; + *) + usage + exit + ;; + esac + shift +done + +MAJOR=$(echo "${VERSION}" | awk -F '.' '{print $1}') +MINOR=$(echo "${VERSION}" | awk -F '.' '{print $2}') +PATCH=$(echo "${VERSION}" | awk -F '.' '{print $3}') + +docker login + +if [ -n "${PUSH_LATEST}" ]; then + docker tag "${NAME}:${VERSION}" "${NAME}:latest" + docker push "${NAME}:latest" +fi + +docker tag "${NAME}:${VERSION}" "${NAME}:${MAJOR}.${MINOR}.${PATCH}" +docker tag "${NAME}:${VERSION}" "${NAME}:${MAJOR}.${MINOR}" +docker tag "${NAME}:${VERSION}" "${NAME}:${MAJOR}" + +docker push "${NAME}:${MAJOR}.${MINOR}.${PATCH}" +docker push "${NAME}:${MAJOR}.${MINOR}" +docker push "${NAME}:${MAJOR}" diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..f0bb29e --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +1.3.0