Skip to main content

Codefresh + Snyk = ship fast and securely

Écrit par:
Antoine Arlaud
Antoine Arlaud
wordpress-sync/Codefresh

11 décembre 2018

0 minutes de lecture

Modern software development is about writing code. Not building, not shipping, but developing — we code, we merge, it builds, it ships. It’s important to test on both sides of the repo frontier, between the code and the automated part. The goal is to test before we merge so the changes go through the pipeline smoothly.

Pipelines are not meant for us developers to test our changes for the first time, it's meant to gate if our code does not satisfy the criteria for shipping the new release. It’s like a final checklist while the rocket is still on the launchpad. In an ideal world, it's even a bit too late if we need to fix things during a pipeline. We’d rather do that during development and/or merging. However, while the world is not always ideal, we should still strive to keep changes in the development phase, as a general rule of thumb.

To achieve this, test earlier to provide feedback as far left as you can, such as on your machine or in your code repository. Of course, you’ll still need to build this pipeline in the first place, integrating the necessary tests. This is where Codefresh comes in. It allows you to build automated pipelines to ship out your apps in containers and Kubernetes deployments.

These tests MUST include security. It's simply a must. If you’re not convinced, stop reading this post and go talk to your security team/mate/peers/meetup group or even us. We’ll each explain why you should care... this is where Snyk comes in.

We are going to take 5-10 minutes and build this pipeline in Codefresh, baking in security testing with Snyk. The testing will be short and sweet so we can go back to coding and ultimately shipping features. Both Codefresh and Snyk have free plans, so you can follow along at no cost. I also gave a webinar session with the folks from Codefresh, so if you prefer to watch the video, you can check it out here.

We are going to build a sample Node app and ship it into a Docker container that we'll push on to Docker Hub. From there, another pipeline will kick in and deploy the output as a Kubernetes application. Codefresh will pick up on changes merged into our repo, and build the Docker images, then run the Snyk tests to determine if it’s safe to proceed shipping the resultant output.

First, let’s have a quick primer on applications and containers. The anatomy of a typical application running on a container infrastructure is illustrated below.

The pipeline will get my application source from my GitHub repo, build it into a Docker container, then test both application dependencies as well as OS dependencies for security issues before pushing it to Docker Hub. Assuming we are vulnerability free, it will not break.

image28

Let's now create this pipeline for real!

1. Sign up for new Codefresh and Snyk.io accounts.

2. Add a repo in Codefresh.

image30

For this demo, we'll use the https://github.com/snyk-playground/codefresh-pipeline-snyk-app-docker-scan repository.

image4

3. Next, select Start from template as your build method.

image24

We’re using a NodeJS application here. Let’s select that and click next.

image16

We’ll use the stock template for now. Feel free to tweak based on your needs.

image22

Alright, our new repo was added, let’s now proceed to defining the pipeline.

image19

4. Now, build the repository by clicking Create pipeline.

Let’s first define some required environment variables. We’ll make sure to encrypt any secrets. Add a new variable called ‘SNYK_TOKEN’.

image25

The value can be found on https://app.snyk.io/account.

image15

While you’re there, add another variable called SNYK_ORG with the value as the end part of your URL https://app.snyk.io/org/. In my case, it’s aarlaud-snyk-demo

image26

That’s all for Snyk.

Now, we’ll need a few additional env vars to pull the image down from the Codefresh registry and also to push the final image to Docker Hub.

For Codefresh to access our private image registry, add the following variables (see for more details here https://codefresh.io/docs/docs/docker-registries/codefresh-registry/#generate-cfcr-login-token):

  • CF_USER_NAME - my Codefresh username (eg.aarlaud for me)

  • CFCR_ACCOUNT - for me, it’s the same as my username aarlaud

  • CFCR_LOGIN_TOKEN - generate a token on https://g.codefresh.io/user/settings, and encrypt it!

For the Docker Hub integration, we’ll store the image name that we want to push our final Docker image into the Hub under:

IMAGE_NAME - for example in my case aarlaudsnyk/trainingapp

Your set of variables should look similar to this:

image27

Lastly, we’ll need the Docker configuration setup in Codefresh Integrations section:

image20

It does require an account on Docker Hub (https://hub.docker.com/), so create one if you have not already done so and set up the Docker registry integration:

image12

Alright, now we are ready to proceed!If you take a look at your triggers, you’ll see that every commit will trigger a build. Note that because we created this pipeline off the master branch, only commits in master will trigger the build. This is good enough for now, and easy to change or duplicate for other branches for other environments.

image2

If we look at the workflow, we will recognize the Docker template that we saw earlier. It might at some point be better to move this into a dockerfile.

image18

Now, let’s try to build this to see if it works as expected before going any further.

image21

You’ll see the build starting:

image3

And now my app has built.

image14

Now, Codefresh automatically puts this image into your own private Docker registry on your Codefresh account.Looking at the Images section, I can see my freshly built Docker image:

image31

Additionally, before simply pushing this image to Docker Hub, I want to ensure that we are free of security vulnerabilities.

5. Going back to the pipeline, let’s add a snyk test command to test the dependencies for any security vulnerabilities.

We’ll first install Snyk and then run it using very simple commands aimed at breaking if we have any high severity issues. I don’t want to break for low or medium so much until I get a better grip on my workflow.

> npm install -g snyk
> snyk test --severity-threshold=high
image13

Hit save, then build it again to run it. You’ll see an extract step in the pipeline for Running Unit Tests. It is probably going to fail because of some issues. We’ll fix them later. Let’s now scan the Docker image itself for the OS component of my app.

When Codefresh builds the app, it pushes the image to our private registry. It then pulls it to run the unit tests. We’ll repeat that process to run a second snyk test targeted at the Docker image.

To do that, we’ll use a Codefresh composition. Look at the docs for more details, but we’ll switch to yaml to give us more capabilities.

image5

Now in yaml mode, let’s first add these 3 lines right after the “version: ‘1.0’”stages:

-scan
-promote

This change is purely cosmetic – it will create a bucket-like section in the UI.Let’s tweak this a bit for our existing RunningUnitTests section to use the SnykAppScan section. Note that besides the title changes, we’ve added a stage section to put this in the right UI bucket, and we are passing more directly the environment variables, cleaning up a few things.

SnykAppScan:
title: Snyk Test Application Dependencies
stage: scan
image: '${{BuildingDockerImage}}'
working_directory: IMAGE_WORK_DIR
environment:
- SNYK_TOKEN=${{SNYK_TOKEN}}
- SNYK_ORG=${{SNYK_ORG}}
commands:
- npm install -g snyk
- snyk test --severity-threshold=high
on_success:
metadata:
set:
- '${{BuildingDockerImage.imageId}}':
- CF_QUALITY: true
on_fail:
metadata:
set:
- '${{BuildingDockerImage.imageId}}':
- CF_QUALITY: false

Now, let’s add a SnykScanImage section, this time for our Docker image scanning portion. Stage is also scan here, so it’s in the same bucket as our app dependencies scanning stage. The composition docs mentioned allow you to run multiple services to run your tests.

In our case, BuildingDockerImage will be the image output of the build stage. We’ll want to test that image from the outside, so we are composing our test with a scan service running another image I previously built, aarlaudsnyk/snyk-container-scan-docker. This image will simply pull together the required pieces for Snyk to perform a test on a Docker image. The source can be seen here:

SnykScanImage:
title: Snyk Test Docker OS Dependencies
stage: scan
type: composition
composition:
version: '2'
services:
targetimage:
image: ${{BuildingDockerImage}} # Must be the Docker build step name
command: sh -c "exit 0"
labels:
build.image.id: ${{CF_BUILD_ID}} # Provides a lookup for the composition
composition_candidates:
scan_service:
image: aarlaudsnyk/snyk-container-scan-docker
command: python snyk-cli.py "${{IMAGE_NAME}}:${{CF_BRANCH_TAG_NORMALIZED}}"
environment:
- SNYK_TOKEN=${{SNYK_TOKEN}}
- SNYK_ORG=${{SNYK_ORG}}
- CFCR_ACCOUNT=${{CFCR_ACCOUNT}}
- CF_USER_NAME=${{CF_USER_NAME}}
- CFCR_LOGIN_TOKEN=${{CFCR_LOGIN_TOKEN}}
depends_on:
- targetimage
volumes: # Volumes required to run DIND
- /var/run/docker.sock:/var/run/docker.sock
- /var/lib/docker:/var/lib/docker
add_flow_volume_to_composition: true
on_success: # Execute only once the step succeeded
metadata: # Declare the metadata attribute
set: # Specify the set operation
- ${{BuildingDockerImage.imageId}}: # Select any number of target images
- SECURITY_SCAN: true

on_fail: # Execute only once the step failed
metadata: # Declare the metadata attribute
set: # Specify the set operation
- ${{BuildingDockerImage.imageId}}: # Select any number of target images
- SECURITY_SCAN: false

The section of interest from the yaml file above is:

image: aarlaudsnyk/snyk-container-scan-docker
command: python snyk-cli.py "${{IMAGE_NAME}}:${{CF_BRANCH_TAG_NORMALIZED}}"

We use that image to then run the Python command which primarily does 2 things:

  • Pulls down the image to test locally

  • Runs ‘snyk test --docker ’

This image is crafted to pull the image from Codefresh’s registry if the following env vars are defined

  • CFCR_ACCOUNT

  • CF_USER_NAME

  • CFCR_LOGIN_TOKEN

This allows us to test the freshly built image. Otherwise it’ll default to attempt to pull down an image from Docker Hub instead.

Lastly, let’s add the Docker Hub registry push at the end, to actually push this image if we are passing our tests successfully:

PushingToDockerRegistry:
title: Pushing to Docker Registry
stage: promote
type: push
candidate: '${{BuildingDockerImage}}'
tag: '${{CF_BRANCH_TAG_NORMALIZED}}'
registry: aarlaudsnyk

Nothing crazy here, Codefresh makes it a breeze to push into Docker Hub as long as the integration has been configured (see a bit earlier).Note the stage being promote to distinguish it from the scan tests.

All together, it should look like something like this

version: '1.0'
stages:
- scan
- promote
steps:
BuildingDockerImage:
title: Building Docker Image
type: build
image_name: ${{IMAGE_NAME}}
working_directory: ./
tag: '${{CF_BRANCH_TAG_NORMALIZED}}'
dockerfile:
content: |-
FROM node:8.0-alpine AS builder
WORKDIR /app
COPY package.json /app
# Creating tar of productions dependencies
RUN npm install --production && cp -rp ./node_modules /tmp/node_modules
# Installing all dependencies
RUN npm install
# Copying application code
COPY . /app
SnykAppScan:
title: Snyk Test Application Dependencies
stage: scan
image: '${{BuildingDockerImage}}'
working_directory: IMAGE_WORK_DIR
environment:
- SNYK_TOKEN=${{SNYK_TOKEN}}
- SNYK_ORG=${{SNYK_ORG}}
commands:
- npm install -g snyk
- snyk test --severity-threshold=high
on_success:
metadata:
set:
- '${{BuildingDockerImage.imageId}}':
- CF_QUALITY: true
on_fail:
metadata:
set:
- '${{BuildingDockerImage.imageId}}':
- CF_QUALITY: false

SnykScanImage:
title: Snyk Test Docker OS Dependencies
stage: scan
type: composition
composition:
version: '2'
services:
targetimage:
image: ${{BuildingDockerImage}} # Must be the Docker build step name
command: sh -c "exit 0"
labels:
build.image.id: ${{CF_BUILD_ID}} # Provides a lookup for the composition
composition_candidates:
scan_service:
image: aarlaudsnyk/snyk-container-scan-docker
command: python snyk-cli.py "${{IMAGE_NAME}}:${{CF_BRANCH_TAG_NORMALIZED}}"
environment:
- SNYK_TOKEN=${{SNYK_TOKEN}}
- SNYK_ORG=${{SNYK_ORG}}
- CFCR_ACCOUNT=${{CFCR_ACCOUNT}}
- CF_USER_NAME=${{CF_USER_NAME}}
- CFCR_LOGIN_TOKEN=${{CFCR_LOGIN_TOKEN}}
depends_on:
- targetimage
volumes: # Volumes required to run DIND
- /var/run/docker.sock:/var/run/docker.sock
- /var/lib/docker:/var/lib/docker
add_flow_volume_to_composition: true
on_success: # Execute only once the step succeeded
metadata: # Declare the metadata attribute
set: # Specify the set operation
- ${{BuildingDockerImage.imageId}}: # Select any number of target images
- SECURITY_SCAN: true

on_fail: # Execute only once the step failed
metadata: # Declare the metadata attribute
set: # Specify the set operation
- ${{BuildingDockerImage.imageId}}: # Select any number of target images
- SECURITY_SCAN: false

PushingToDockerRegistry:
title: Pushing to Docker Registry
stage: promote
type: push
candidate: '${{BuildingDockerImage}}'
tag: '${{CF_BRANCH_TAG_NORMALIZED}}'
registry: aarlaudsnyk

6. Let's try building now, so we can get a baseline. We can see our organization looks pretty clean:

image11

After a moment, we get this

image8

Uh oh, looks like we have some issues in our app — it safely stopped the pipeline before shipping something with security issues in it. Let's fix that by following the remediation steps indicated in the output. Bumping the qs package to version 6.0.4 in the package.json will do the trick.

Let's run this again.

image7

We cleared the qs vulnerability, yay! But we now we have something going on with the OS dependencies in our Docker image.

image17

The remediation indication shows that bumping the image should be a good way to go (check out snyk.io/docs in Docker section). So let's bump to node:10-alpine and try this again.

image9

Save => Build => Doing push-ups while building!

image23

Sweet! We now have a fresh baseline and a fully functioning pipeline shipping our app+container to Docker Hub.

We can now go back to coding. Let's make sure we run snyk test for our changes before we merge and/or test our changes using Snyk’s GitHub PR integration. From there the changes will be shipped automatically. Extra bonus, latest addition!

Use snyk monitor to keep an eye on your dependencies over time. Nobody likes babysitting projects, so by running snyk monitor, Snyk will automatically notify us if a new vulnerability has been disclosed for any of the dependencies in the project or Docker image. We just need to add snyk monitor after the snyk test section.

image6

You’ll then see the url of the snapshot in the output for monitoring:

image29

The Docker portion is going to be done automatically if the snyk test passes successfully (meaning there are no known issues). Monitoring snapshot links are also provided.

image10

You can also see the monitored projects on Snyk UI in your Snyk organization:

aarlaud-snyk-demo

See the Codefresh pipeline by clicking the badge in the repo (https://github.com/snyk-playground/codefresh-pipeline-snyk-app-docker-scan).

That’s all folks! Stay Secure!

Publié dans: