This is a companion repo with code samples for https://www.alexandraulsh.com/2018/06/25/docker-npmrc-security/, a blog post I wrote about using .npmrc files securely in Docker images.
To build these example Docker images you'll need git, Node.js, npm, an npm account, and Docker. You'll need to set an NPM_TOKEN environment variable so you can pass it as a build argument to Docker.
git clone https://github.com/alulsh/docker-npmrc-security.gitorgit clone [email protected]:alulsh/docker-npmrc-security.gitcd docker-npmrc-security
- Install Node.js and npm. I recommend using nvm.
- Sign up for an account on npmjs.com.
- Run
npm token create --read-onlyto create a read-only npm token. - Run
export NPM_TOKEN=<npm token>to set your npm token as an environment variable.
Download the version of Docker CE for your operating system. The BuildKit mode --secret flag requires Docker 18.09 and later.
To build this image, run docker build . -f Dockerfile-insecure-1 -t insecure-app-1 --build-arg NPM_TOKEN=$NPM_TOKEN.
ARG NPM_TOKEN
RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
RUN npm install
The .npmrc file is never deleted from this image. The .npmrc file is on the file system of any containers created from this image.
- Run
docker run -it insecure-app-1 ashto start the container. We need to useashinstead ofbashsince we're running Alpine Linux. - Run
ls -al. You should see an.npmrcfile in the/private-appdirectory. - Run
cat .npmrc.
To build this image, run docker build . -f Dockerfile-insecure-2 -t insecure-app-2 --build-arg NPM_TOKEN=$NPM_TOKEN.
ARG NPM_TOKEN
RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
RUN npm install
RUN rm -f .npmrc
The .npmrc file is deleted from this Docker image but in a separate RUN instruction. Each RUN instruction creates a new Docker layer (intermediate image). If an attacker has access to the Docker daemon or obtains a copy of our image then they can steal the .npmrc file from the layers of the Docker image.
- Run
docker save insecure-app-2 -o ~/insecure-app-2.tarto save the Docker image as a tarball. - Run
mkdir ~/insecure-app-2 && tar xf ~/insecure-app-2.tar -C ~/insecure-app-2to untar to~/insecure-app-2. - Run
cd ~/insecure-app-2. - Run
for layer in */layer.tar; do tar -tf $layer | grep -w .npmrc && echo $layer; done. You should see a list of layers with.npmrcfiles. - Run
tar xf <layer id>/layer.tar private-app/.npmrcto extractprivate-app/.npmrcfrom the layer tarball. - Run
cat private-app/.npmrcto view the.npmrcfile and npm token.
To build this images, run docker build . -f Dockerfile-insecure-3 -t insecure-app-3 --build-arg NPM_TOKEN=$NPM_TOKEN.
ARG NPM_TOKEN
RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc && \
npm install && \
rm -f .npmrc
The .npmrc file is created, used, and deleted in the same RUN instruction and Docker layer. Since we passed in the npm token as a build argument (ARG NPM_TOKEN) our npm tokens are still leaked in the Docker image commit history. If the attacker gains access to the Docker daemon or obtains a copy of our Docker image then they can steal our npm tokens using docker history.
- Run
docker history insecure-app-3.
To build this image, run docker build . -f Dockerfile-secure-multistage -t secure-app-multistage --build-arg NPM_TOKEN=$NPM_TOKEN.
This Dockerfile uses multi-stage builds to protect our .npmrc file. In the first stage build, we create our .npmrc, run npm install, and delete our .npmrc. We then copy over our built Node application to our second stage build. We can use the same base image - node:8.11.3-alpine - for both stages of our build.
To verify that this Docker image does not leak our npm tokens, run docker history secure-app-multistage.
To build this image, run DOCKER_BUILDKIT=1 docker build . -f Dockerfile-secure-secrets -t secure-app-secrets --secret id=npm,src=$HOME/.npmrc. You can also run export DOCKER_BUILDKIT=1 to enable BuildKit, then run docker build . -f Dockerfile-secure-secrets -t secure-app-secrets --secret id=npm,src=$HOME/.npmrc.
This Dockerfile uses the --secret flag for docker build released with Docker 18.09. It uses the experimental RUN --mount=type=secret syntax from the experimental Docker frontend for BuildKit. This Docker CLI pull request added support for --secret to docker build in August 2018.
To verify that this Docker image does not leak our npm tokens, run docker history secure-app-secrets.