Hands up if this has happened to you. You're reading a well-written article on one of countless topics, and you get to the line that goes something like this:
// DO NOT DO THIS IN A PRODUCTION APP const API_KEY = '<api-key-displayed-in-plain-text>'
Ok, so how should you be doing this? Unfortunately, there isn't a one-size-fits-all approach to securing your secrets. Different programming languages deployed in different environments all handle secrets in their own way.
Suffice it to say that you should never store secrets in your code or repository. Secrets should be passed into your app through environment variables at the last possible moment.
I have been using Bitbucket Pipelines since it was in Alpha and I have to say, it's fantastic. It has to be the quickest and easiest way to setup continuous delivery right from your repo.
Pipelines are configured with YAML files and can be very simple or extremely complex depending on your needs.
I like to break up my build jobs into steps for a couple of reasons:
Here is a 3-step bitbucket-pipelines.yml file that takes a create-react-app site, packages it as a Docker image and deploys it to a Kubernetes cluster:
options: # Enable docker for the Pipeline docker: true pipelines: branches: master: - step: name: Build app for Production (create-react-app) image: mhart/alpine-node:10 caches: - node script: # Install Dependencies - npm install # Run our Tests - npm run test # Package App for Production - npm run build artifacts: # Pass the "build" Directory to the Next Step - build/** - step: name: Build Docker Image script: # NOTE: Set $DOCKER_HUB_USERNAME and $DOCKER_HUB_PASSWORD as environment SECRETS in Bitbucket repository settings # Use $BITBUCKET_COMMIT to tag our docker image - export IMAGE_NAME=<docker-username>/<docker-image>:$BITBUCKET_COMMIT # Build the Docker image (this will use the Dockerfile in the root of the repo) - docker build -t $IMAGE_NAME . # Authenticate with the Docker Hub registry - docker login -u $DOCKER_HUB_USERNAME -p $DOCKER_HUB_PASSWORD # Push the new Docker image to the Docker registry - docker push $IMAGE_NAME - step: # trigger: manual name: Deploy to Kubernetes image: atlassian/pipelines-kubectl script: # NOTE: $KUBECONFIG is secret stored as a base64 encoded string # Base64 decode our kubeconfig file into a temporary kubeconfig.yml file (this will be destroyed automatically after this step runs) - echo $KUBECONFIG | base64 -d > kubeconfig.yml # Tell our Kubernetes deployment to use the new Docker image tag - kubectl --kubeconfig=kubeconfig.yml --namespace=<namespace> set image deployment/<deployment-name> <deployment-name>=<docker-username>/<docker-image>:$BITBUCKET_COMMIT
FROM mhart/alpine-node:10 WORKDIR /app EXPOSE 5000 # Install http server RUN yarn global add serve # Bundle app source COPY build /app/build # Run serve CMD [ "serve", "-n", "-s", "build" ]
Here's the important part of all that:
- echo $KUBECONFIG | base64 -d > kubeconfig.yml
Our kubeconfig file is stored as a Base64 encoded string in a Bitbucket secret named
Bitbucket secrets are stored encrypted, and decrypted when you call the variable in pipelines.
We decode the
$KUBECONFIG variable and store it in a temporary file called kubeconfig.yml which is automatically deleted as soon as this step completes.
This entire build takes less than 1 minute 40 seconds and using Alpine Node the Docker image is just 29 MB.
Securing your secrets isn't hard, but it starts with knowing where to look.
Some tips for securing secrets in different Node.js environments: