4 min read

How to run a public docker container with a private OAuth token

Etienne Marais

Dockerhub makes it incredibly easy to host docker builds for re-use. You can setup your ideal world software environment in a container and ship that anywhere really.

In this instance I needed to run unit tests against a PHP 5.6 composer project. I wanted to run the tests in a way that I could get the coverage reports from them so it is a bit of a special way of doing it. Mainly because I needed to copy the results files out of the running container before it shuts down.

The problem I ran into is in the realm of infosec and specifically related to private github oauth tokens. When you work for a company your SRE or Platform team will have an amount of valid github tokens that will get used for company projects. This goes without saying but you shouldn't leave those lying around in public space.

There are a couple of issues I wanted to address here. First, having a private token in a public docker build is advised against. Having private tokens in any public space is advised against. Even though it's not entirely obvious, your token gets built into the image at build time. This means that by running env | sort inside of a running container you will be able to see all the environmental variables, along with your private token. Having that baked into your public image opens your projects up to exploits.

Secondly is to understand the docker build time vs run time differences. If you can't bake a token in, then where to next?

Docker entrypoints provide a great mechanism to solve these issues. By providing a placeholder environment variable and running the installation code, like composer install, you can safely build the base public image, but only provide the token at run time. This is beneficial because you have full control over when your token gets used and it's scope is isolated to local or build servers.

I have provided the code below consisting of a Dockerfile, an entrypoint.sh and a run_test.sh file that shows how the flow happens.

Code

Dockerfile

Alpine, PHP 5.6, Composer with an entrypoint. I am building a light PHP 5.6 image on top of alpine with composer installed on the path. Setting up a work directory and providing a space for a environment variable to contain the value of the github private token.

# https://github.com/graze/docker-php-alpine
FROM graze/php-alpine:5.6-test
MAINTAINER Etienne Marais <[email protected]>

# Add more dependencies
RUN apk add --no-cache curl

# Create a working directory
RUN mkdir -p /usr/src
WORKDIR /usr/src

# Set environment build arguments
ENV GITHUB_OAUTH_TOKEN $GITHUB_OAUTH_TOKEN

# Install composer
ENV COMPOSER_HOME /composer
ENV PATH /composer/vendor/bin:$PATH
ENV COMPOSER_ALLOW_SUPERUSER 1
RUN curl -o /tmp/composer-setup.php https://getcomposer.org/installer && \
    curl -o /tmp/composer-setup.sig https://composer.github.io/installer.sig && \
    php -r "if (hash('SHA384', file_get_contents('/tmp/composer-setup.php')) !== trim(file_get_contents('/tmp/composer-setup.sig'))) { unlink('/tmp/composer-setup.php'); echo 'Invalid installer' . PHP_EOL; exit(1); }" && \
    php /tmp/composer-setup.php --no-ansi --install-dir=/usr/local/bin --filename=composer --version=1.4.2 && rm -rf /tmp/composer-setup.php && \
    composer global require "hirak/prestissimo:^0.3" # Yarn for PHP

# Add Entrypoint
ADD entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

entrypoint.sh

In the entrypoint, I set the composer config to use that github token defined in the dockerfile. This will allow your composer builds to not be rate limited by github.

#!/bin/sh
set -efu
set -o pipefail

SRC_DIR=/usr/src

# Set OAuth token from the environment
composer config -g github-oauth.github.com $GITHUB_OAUTH_TOKEN

cd $SRC_DIR

# Install dependencies via the entrypoint
composer install --no-scripts --no-interaction

# Execute the given run command
exec "$@"

Use it from your environment, Laptop or build server:

run_test.sh

This is the example where I run the whole thing. First, we want to set some variables in our bash script. BASE_DIR sets the base directory from the git project and adds the current directory as the absolute path.

The GITHUB_OAUTH_TOKEN variable is the one that will set the token in the environment for all the steps to a running docker container.

The PROJECT_CONTAINER variable is just the project name.

Notice the create part, which is the most important in this case:

docker create --name $PROJECT_CONTAINER -e GITHUB_OAUTH_TOKEN=$GITHUB_OAUTH_TOKEN etiennemarais/php-alpine-tests:5.6 ./vendor/bin/phpunit

The -e flag sets the token from your local/build environment. This will pass the variable all the way down to the entrypoint where it's needed.

#!/bin/bash
set -efu
set -o pipefail

# Gets the top level project root from git
BASE_DIR=$(git rev-parse --show-toplevel 2>/dev/null || echo $PWD)
GITHUB_OAUTH_TOKEN=<THIS WILL BE YOUR GITHUB TOKEN>
PROJECT_CONTAINER="test-project"

# Create a docker container
docker create --name $PROJECT_CONTAINER -e GITHUB_OAUTH_TOKEN=$GITHUB_OAUTH_TOKEN etiennemarais/php-alpine-tests:5.6 ./vendor/bin/phpunit

# Manually copy in your project files
docker cp $(pwd)/. $PROJECT_CONTAINER:/usr/src

# Attach, we want to see the output in jenkins console
docker start $PROJECT_CONTAINER -a 

# Manually stop the container after it has run
docker stop $PROJECT_CONTAINER

# Manually remove the container
docker rm $PROJECT_CONTAINER