- Github : https://github.com/clemenko
- Twitter : @clemenko
- Email : clemenko@gmail.com
In this lab you will integrate Docker Enterprise in to your development pipeline. You will push an image to the Docker Trusted Registry (DTR). DTR will scan your image for vulnerabilities so they can be fixed before your application is deployed. This helps you build more secure apps!
Difficulty: Intermediate
Time: Approximately 90 minutes
Tasks:
- Who am i
- Document conventions
- Abbreviations
- Prerequisites
- Understanding the Play With Docker Interface
- Introduction
- Task 1 Accessing PWD
- Task 2 Enable Docker Image Scanning
- Task 3 Create Jenkins User and Organization
- Task 4: Create DTR Repositories
- Task 5 Pull / Tag / Push Docker Image
- Task 6 Review Scan Results
- Task 7 Extend with Image Mirroring
- Task 8 Docker Content Trust / Image Signing
- Task 9 Automate with Jenkins
- Conclusion
When you encounter a phrase in between <
and >
you are meant to substitute in a different value.
We are going to leverage the power of Play With Docker.
The following abbreviations are used in this document:
- UCP = Universal Control Plane
- DTR = Docker Trusted Registry
- DCT = Docker Content Trust
- CVE = Common Vulnerabilities and Exposures
- PWD = Play With Docker
This lab requires an instance of Docker Enterprise. Docker Enterprise includes Docker Universal Control Plane and Docker Trusted Registry. This lab provides Docker Enterprise.
This workshop is only available to people in a pre-arranged workshop. That may happen through a Docker Meetup, a conference workshop that is being led by someone who has made these arrangements, or special arrangements between Docker and your company. The workshop leader will provide you with the URL to a workshop environment that includes Docker Enterprise. The environment will be based on Play with Docker.
If none of these apply to you, contact your local Docker Meetup Chapter and ask if there are any scheduled workshops. In the meantime, you may be interested in the labs available through the Play with Docker Classroom.
There are three main components to the Play With Docker (PWD) interface.
Play with Docker provides access to the 3 Docker Enterprise hosts in your Cluster. These machines are:
- A Linux-based Docker Enterprise 2.1 (UCP 3.1.6 & DTR 2.6.5 & 18.09.2) Manager node
- Three Linux-based Docker Enterprise 2.1 (18.09.2) Worker nodes
By clicking a name on the left, the console window will be connected to that node.
Additionally, the PWD screen provides you with a one-click access to the Universal Control Plane (UCP)
web-based management interface as well as the Docker Trusted Registry (DTR) web-based management interface. Clicking on either the UCP
or DTR
button will bring up the respective server web interface in a new tab.
Throughout the lab you will be asked to provide either hostnames or login credentials that are unique to your environment. These are displayed for you at the bottom of the screen.
Note: There are a limited number of lab connections available for the day. You can use the same session all day by simply keeping your browser connection to the PWD environment open between sessions. This will help us get as many people connected as possible, and prevent you needing to get new credentials and hostnames in every lab. However, if you do lose your connection between sessions simply go to the PWD URL again and you will be given a new session.
This workshop is designed to demonstrate the power of Docker Secrets, Image Promotion, Scanning Engine, and Content Trust. We will walk through creating a few secrets. Deploying a stack that uses the secret. Then we will create a Docker Trusted Registry repository where we can create a promotion policy. The promotion policy leverages the output from Image Scanning result. This is the foundation of creating a Secure Supply Chain. You can read more about secure supply chains for our Secure Supply Chain reference architecture.
-
Navigate in your web browser to the URL the workshop organizer provided to you. Chrome is advised!
-
Fill out the form, and click
submit
. You will then be redirected to the PWD environment. It may take a minute or so to provision out your PWD environment.
We are going to use worker3
for ALL our command line work. Click on worker3
to activate the shell.
Now we need to setup a few variables. We need to create DTR_URL
and DTR_USERNAME
. But the easiest way is to clone the Workshop Repo and run script.
git clone https://github.com/clemenko/dc19_supply_chain.git
Once cloned, now we can run the var_setup.sh
script.
source dc19_supply_chain/scripts/var_setup.sh
Now your PWD environment variables are setup. We will use the variables for some scripting.
Before we create the repositories, let's start with enabling the Docker Image Scanning engine.
-
From the main PWD screen click the
DTR
button on the left side of the screenNote: Because this is a lab-based install of Docker Enterprise we are using the default self-signed certs. Because of this your browser may display a security warning. It is safe to click through this warning.
In a production environment you would use certs from a trusted certificate authority and would not see this screen.
-
Select
Enable Scanning
. Leave it inOnline
mode and selectEnable
. Press the buttonEnable Online Scanning
. The CVE database will start downloading. This can take a few minutes. Please be patient for it to complete.
In order to setup our automation we need to create an organization and a user account for Jenkins. We are going to create a user named jenkins
in the organization ci
.
-
Once in
DTR
navigate toOrganizations
on the left. -
Now click
New organization
.
Now we should see the organization named ci
.
While remaining in DTR we can create the user from here.
-
Click on the organization
ci
. -
Click
Add user
. -
Make sure you click the radio button
New
. Add a new user namejenkins
. Set a simple password that you can remember. Maybeadmin1234
?
Now change the permissions for the jenkins
account to Org Owner
.
Now that we have the jenkins
user created we need to add a token for use with DTR's API.
Navigate to Users
on the left pane. Click on jenkins
, then click the Access Tokens
tab.
Click New access token
. Enter Supply Chain
into the description field and click Create
.
Write down the token that is displayed. You will need this again!
It should look like ee9d7ff2-6fd4-4a41-9971-789e06e0d5d5
. Click Done
.
Lets add it to the worker3
environment. Replace <TOKEN>
with the token from DTR.
#example
#export DTR_TOKEN=ee9d7ff2-6fd4-4a41-9971-789e06e0d5d5
export DTR_TOKEN=<TOKEN>
We now need to access Docker Trusted Registry to setup two repositories.
We have an easy way with a script or the hard way by using the GUI.
Either way we need to create two repositories, dc19_build
and dc19
. dc19_build
will be used for the private version of the image. dc19
will be the public version once an CVE scan is complete.
Easy Way:
Since we used git clone
to copy the repository to worker3
for this workshop, there is a script from that will create the DTR repositories.
./dc19_supply_chain/scripts/create_repos.sh
Feel free to cat
the file to see how we are using curl
and the API to create the repositories.
[worker3] (local) root@10.20.0.38 ~
$ cat dc19_supply_chain/scripts/create_repos.sh
#!/bin/bash
# requires environment variables: DTR_HOST, DTR_USERNAME and DTR_TOKEN
if [ -z "$DTR_TOKEN" ]; then
echo " Please create a DTR_TOKEN variable before preceding..."
exit
fi
curl -X POST -k -L \
-u $DTR_USERNAME:$DTR_TOKEN \
https://$DTR_URL/api/v0/repositories/ci \
-H 'Content-Type: application/json' \
-d '{
"enableManifestLists": true,
"immutableTags": true,
"longDescription": "",
"name": "dc19",
"scanOnPush": true,
"shortDescription": "Dockercon 2019 Example - public",
"visibility": "public"
}'
curl -X POST -k -L \
-u $DTR_USERNAME:$DTR_TOKEN \
https://$DTR_URL/api/v0/repositories/ci \
-H 'Content-Type: application/json' \
-d '{
"enableManifestLists": true,
"immutableTags": true,
"longDescription": "",
"name": "dc19_build",
"scanOnPush": true,
"shortDescription": "Dockercon 2019 Example - private",
"visibility": "public"
}'
Hard Way:
-
Navigate to
Repositories
on the left menu and clickNew repository
. -
Create that looks like
ci
/dc19_build
. Make sure you clickPrivate
. Do not clickCreate
yet! -
Click
Show advanced settings
and then clickOn Push
underSCAN ON PUSH
. This will ensure that the CVE scan will start right after every push to this repository. And turn onIMMUTABILITY
. Then clickCreate
. -
Repeat this for creating the
ci
/dc19
Public
repository withSCAN ON PUSH
set toOn Push
andIMMUTABILITY
turnedOff
.
With the two repositories setup we can now define the promotion policy. The first policy we are going to create is for promoting an image that has passed a scan with zero (0) Critical vulnerabilities. The policy will target the ci
/dc19
repository.
-
Navigate to the
ci
/dc19_build
repository. ClickPromotions
and clickNew promotion policy
. Note: Make sure theIs source
box is selected. -
In the
PROMOTE TO TARGET IF...
box selectCritical Vulnerabilities
and then checkless than or equals
. In the box belowequals
enter the number zero (4) and clickAdd
. -
Set the
TARGET REPOSITORY
toci
/dc19
and clickSave & Apply
.
When we push an image to ci
/dc19_build
it will get scanned. Based on that scan report we could see the image moved to ci
/dc19
. Lets push a few images to see if it worked.
Lets pull, tag, and push a few images to YOUR DTR.
In order to push and pull images to DTR we will need to take advantage of PWD's Console Access.
-
Navigate back to the PWD tab in your browser.
-
Click on
worker3
. -
In the console we should already have a variable called
DTR_URL
. Lets check.echo $DTR_URL
If you are not sure please follow Task 1.1 Set Up Environment Variables.
-
Now we login to our DTR server using your
DTR_TOKEN
from Task 3.3 Create Jenkins DTR Token.docker login -u jenkins -p $DTR_TOKEN $DTR_URL
-
Now we can start pulling a few images.
docker pull clemenko/dc19:0.1 docker pull clemenko/dc19:0.2 docker pull clemenko/dc19:0.3 docker pull alpine
This command is pull a few images from hub.docker.com.
-
Now let's tag the image for our DTR instance. We will use the
URL
variable we set before.docker tag clemenko/dc19:0.1 $DTR_URL/ci/dc19_build:0.1 docker tag clemenko/dc19:0.2 $DTR_URL/ci/dc19_build:0.2 docker tag clemenko/dc19:0.3 $DTR_URL/ci/dc19_build:0.3 docker tag alpine $DTR_URL/ci/dc19_build:alpine
-
Now we can
docker push
the images to DTR.docker push $DTR_URL/ci/dc19_build:0.1 docker push $DTR_URL/ci/dc19_build:0.2 docker push $DTR_URL/ci/dc19_build:0.3 docker push $DTR_URL/ci/dc19_build:alpine
Lets take a good look at the scan results from the images. Please keep in mind this will take a few minutes to complete.
-
Navigate to DTR -->
Repositories
-->ci/dc19_build
-->Tags
.Don't worry if you see images in a
Scanning...
orPending
state. Please click to another tab and click back. -
Take a look at the details to see exactly what piece of the image is vulnerable.
Click
View details
for an image that has vulnerabilities. How about0.2
? There are two views for the scanning results, Layers and Components. The Layers view shows which layer of the image had the vulnerable binary. This is extremely useful when diagnosing where the vulnerability is in the Dockerfile.The vulnerable binary is displayed, along with all the other contents of the layer, when you click the layer itself. In this example there are a few potentially vulnerable binaries:
Now we have a chance to review each vulnerability by clicking the CVE itself, example
CVE-2019-3822
. This will direct you to Mitre's site for CVEs.Now that we know what is in the image. We should probably act upon it.
If we find that they CVE is a false positive. Meaning that it might be disputed, or from OS that you are not using. If this is the case we can simply Hide
the vulnerability. This will not remove the fact that the CVE was found.
Click Show layers affected
and then you can click Hide
for the one critical CVE.
If we click back to Tags
we can now see that the image does not have a critical vulnerability.
Once we have hidden some CVEs we might want to perform a manual promotion of the image.
Docker Trusted Registry allows you to create mirroring policies for a repository. When an image gets pushed to a repository and meets a certain criteria, DTR automatically pushes it to repository in another DTR deployment or Docker Hub.
This not only allows you to mirror images but also allows you to create image promotion pipelines that span multiple DTR deployments and datacenters. Let's set one up. How about we mirror an image to hub.docker.com?
-
Go to hub.docker.com and create an login and repository.
-
Navigate to
Repositories
-->ci
/dc19
-->MIRRORS
-->New mirror
. Change theREGISTRY TYPE
toDocker Hub
and fill out the relevant information like: -
Click
Connect
and scroll down. -
Next create a
tag name
Trigger that is equal topromoted
-
Leave the
%n
tag renaming the same.
Since we already had an image that had the tag promoted
we should see that the image was pushed to hub.docker.com. In fact we can click on the hub repository name to see if the image push was successful.
Docker Content Trust/Notary provides a cryptographic signature for each image. The signature provides security so that the image requested is the image you get. Read Notary's Architecture to learn more about how Notary is secure. Since Docker Enterprise is "Secure by Default," Docker Trusted Registry comes with the Notary server out of the box.
We can create policy enforcement within Universal Control Plane (UCP) such that ONLY signed images from the ci
team will be allowed to run. Since this workshop is about DTR and Secure Supply Chain we will skip that step.
Let's sign your first Docker image?
-
Right now you should have a promoted image
$DTR_URL/ci/dc19:0.2
. We need to tag it with a newsigned
tag.docker pull $DTR_URL/ci/dc19:0.2 docker tag $DTR_URL/ci/dc19:0.2 $DTR_URL/ci/dc19:signed
-
Now lets use the Trust command... It will ask you for a BUNCH of passwords. Do yourself a favor in this workshop and use
admin1234
. :Ddocker trust sign $DTR_URL/ci/dc19:signed
Here is an example output:
[worker3] (local) root@10.20.0.42 ~ $ docker trust sign $DTR_URL/ci/dc19:signed You are about to create a new root signing key passphrase. This passphrase will be used to protect the most sensitive key in your signing system. Please choose a long, complex passphrase and be careful to keep the password and the key file itself secure and backed up. It is highly recommended that you use a password manager to generate the passphrase and keep it safe. There will be no way to recover this key. You can find the key in your config directory. Enter passphrase for new root key with ID b975982: Repeat passphrase for new root key with ID b975982: Enter passphrase for new repository key with ID 61a14ae: Repeat passphrase for new repository key with ID 61a14ae: Enter passphrase for new jenkins key with ID ab5049d: Repeat passphrase for new jenkins key with ID ab5049d: Created signer: jenkins Finished initializing signed repository for ip172-18-0-5-bfu00sinjdg00099igu0.direct.ee-beta2.play-with-docker.com/ci/dc19:signed Signing and pushing trust data for local image ip172-18-0-5-bfu00sinjdg00099igu0.direct.ee-beta2.play-with-docker.com/ci/dc19:signed, may overwrite remote trust data The push refers to repository [ip172-18-0-5-bfu00sinjdg00099igu0.direct.ee-beta2.play-with-docker.com/ci/dc19] af9af2170d23: Layer already exists cd9a82baa926: Layer already exists c60ea83f6a45: Layer already exists cd7100a72410: Layer already exists signed: digest: sha256:5554013b565fc0ccf080f7cf4ad096ffb1dbc4f83496a86f9efa1252f26ed455 size: 1156 Signing and pushing trust metadata Enter passphrase for jenkins key with ID ab5049d: Successfully signed ip172-18-0-5-bfu00sinjdg00099igu0.direct.ee-beta2.play-with-docker.com/ci/dc19:signed [worker3] (local) root@10.20.0.42 ~
Again please use the same password. It will simplify this part of the workshop.
-
And we can confirm the signature has been applied by inspecting the image:
docker trust inspect $DTR_URL/ci/dc19:signed
Here is the example output:
[worker3] (local) root@10.20.0.42 ~ $ docker trust inspect $DTR_URL/ci/dc19:signed [ { "Name": "ip172-18-0-5-bfu00sinjdg00099igu0.direct.ee-beta2.play-with-docker.com/ci/dc19:signed", "SignedTags": [ { "SignedTag": "signed", "Digest": "5554013b565fc0ccf080f7cf4ad096ffb1dbc4f83496a86f9efa1252f26ed455", "Signers": [ "jenkins" ] } ], "Signers": [ { "Name": "jenkins", "Keys": [ { "ID": "ab5049def46b1b8070891981afe6091f95bf9017cdfc447866917f342810a302" } ] } ], "AdministrativeKeys": [ { "Name": "Root", "Keys": [ { "ID": "59eaa1440dfc9fbf709a9640e8b8fbcb636b019f6f70aa90451f361bbd1ecf58" } ] }, { "Name": "Repository", "Keys": [ { "ID": "61a14ae35425dde74dc5d18b292c613f613b357051862c18ca5d0a02a2f0d04e" } ] } ] } ] [worker3] (local) root@10.20.0.42 ~
-
Back in DTR, Navigate to
Repositories
-->ci
/dc19
-->Tags
and you will now see the newsigned
tag with the textSigned
under theSigned
column: -
If you were to enable Docker Content Trust in UCP then you would need to upload the public certificate used to sign the image. As we did not perform the
docker trust signer add
command before step 2 above then a public certificate is automatically generated but is not associated to a user in UCP. This means when UCP tries to verify the signature on a signed image to a user it will fail and therefor not meet UCP's Content Trust policy.To resolve this issue you can upload the base64 encoded public certificate in
~/.docker/trust/tuf/$DTR_URL/ci/dc19/metadata/targets.json
- the certificate is located in the structure.signed.delegations.keys
with the key value ofpublic
.For example, use the command
cat ~/.docker/trust/tuf/$DTR_URL/ci/dc19/metadata/targets.json | jq '.signed.delegations.keys' | grep public
to extract the certificate.
In order to automate we need to deploy Jenkins. If you want I can point you to a few Docker Compose yamls. OR we have the easy way. The easy, aka script, deploys Jenkins quickly.
-
Take a look at the script. Also notice the script will check variables, and then runs
docker run
.cat ./dc19_supply_chain/scripts/jenkins.sh
-
Then run unset Docker Content Trust and instal Jenkins.
./dc19_supply_chain/scripts/jenkins.sh
-
Pay attention to the url AND Jenkins password. It will look like :
[worker3] (local) root@10.20.0.25 ~/ $ dc19_supply_chain/scripts/jenkins.sh ========================================================================================================= Jenkins URL : http://ip172-18-0-20-bcelih5dffhg00b2thog.direct.ee-beta2.play-with-docker.com:8080 ========================================================================================================= Waiting for Jenkins to start................ ========================================================================================================= Jenkins Setup Password = d32eda1cf2464b818826fd82b4f7c2cb =========================================================================================================
-
Now navigate to
http://$DOCS_URL:8080
by clicking on the url in the terminal. Let's start the setup of Jenkins and enter the password. It may take a minute or two for theUnlock Jenkins
page to load. Be patient. -
We don't need to install all plugins at this point. Click
none
at the top. -
Next Click
Continue as admin
in the lower right hand corner. We don't need to create another username for Jenkins. -
And we are done installing Jenkins. Click
Start using Jenkins
Now that we have Jenkins setup and running we need to add 3 additional plugins - Blue Ocean, Generic Webhook Trigger and Pipeline:
-
Click on
Manage Jenkins
-->Manage Plugins
-->Available
and filter/search forBlue Ocean
,Generic Webhook Trigger
andPipeline
. When you have found each one check the checkbox to the left of the plugin name to select for installation. -
Click on
Install without restart
and wait for the plugins to install. When all plugins have installed navigate back to the Jenkins homepage. -
Enter a name like
ci_dc19
, clickFreestyle project
and then clickOK
. -
Let's scroll down to the
Build
section. We will come back to theBuild Triggers
section in a bit. Now clickAdd build step
-->Execute shell
. -
You will now see a text box. Past the following build script into the text box.
Please replace the <DTR_URL> with your URL!
echo $DTR_URL
<--worker3
DTR_USERNAME=admin DTR_URL=<DTR_URL> docker login -u admin -p admin1234 $DTR_URL docker pull clemenko/dc19:0.2 docker tag clemenko/dc19:0.2 $DTR_URL/ci/dc19_build:jenkins_$BUILD_NUMBER docker push $DTR_URL/ci/dc19_build:jenkins_$BUILD_NUMBER docker rmi clemenko/dc19:0.2 $DTR_URL/ci/dc19_build:jenkins_$BUILD_NUMBER
Now scroll down and click
Save
. -
You can watch the output of the
Build
by clicking on the task number in theBuild History
and then selectingBuild Output
-
The console output will show you all the details from the script execution.
-
Review the
ci
/dc19
repository in DTR. You should now see a bunch of tags that have been promoted.
Now that we have Jenkins setup we can extend with webhooks. In Jenkins speak a webhook is simply a build trigger. Let's configure one.
-
Navigate to Jenkins and click on the project/item called
ci_dc19
and click onConfigure
on the left hand side. -
Then scroll down to
Build Triggers
. Check the checkbox forTrigger builds remotely
and enter a Token ofdc19_rocks
. Scroll down and clickSave
. -
Now in your browser goto YOUR
http://$DOCS_URL:8080/job/ci_dc19/build?token=dc19_rocks
It should look like:
http://ip172-18-0-9-bis91vft0fgg00ctq3i0.direct.ee-beta2.play-with-docker.com:8080/job/ci_dc19/build?token=dc19_rocks
-
Check DTR to verify the images were pushed. Then log into
https:hub.docker.com
to see if your images were mirrored.
In this workshop we were able use the tools that are included with Docker Trusted Registry to build a basic Automated Secure Supply Chain. Hopefully with this foundation you can build your own organizations Secure Supply Chain!