docs/ee/ucp/admin/configure/_site/use-trusted-images-for-ci.html

140 lines
10 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<p>The document provides a minimal example on setting up Docker Content Trust (DCT) in
Universal Control Plane (UCP) for use with a Continuous Integration (CI) system. It
covers setting up the necessary accounts and trust delegations to restrict only those
images built by your CI system to be deployed to your UCP managed cluster.</p>
<h2 id="set-up-ucp-accounts-and-teams">Set up UCP accounts and teams</h2>
<p>The first step is to create a user account for your CI system. For the purposes of
this document we will assume you are using Jenkins as your CI system and will therefore
name the account “jenkins”. As an admin user logged in to UCP, navigate to “User Management”
and select “Add User”. Create a user with the name “jenkins” and set a strong password.</p>
<p>Next, create a team called “CI” and add the “jenkins” user to this team. All signing
policy is team based, so if we want only a single user to be able to sign images
destined to be deployed on the cluster, we must create a team for this one user.</p>
<h2 id="set-up-the-signing-policy">Set up the signing policy</h2>
<p>While still logged in as an admin, navigate to “Admin Settings” and select the “Content Trust”
subsection. Select the checkbox to enable content trust and in the select box that appears,
select the “CI” team we have just created. Save the settings.</p>
<p>This policy will require that every image that referenced in a <code class="highlighter-rouge">docker image pull</code>,
<code class="highlighter-rouge">docker container run</code>, or <code class="highlighter-rouge">docker service create</code> must be signed by a key corresponding
to a member of the “CI” team. In this case, the only member is the “jenkins” user.</p>
<h2 id="create-keys-for-the-jenkins-user">Create keys for the Jenkins user</h2>
<p>The signing policy implementation uses the certificates issued in user client bundles
to connect a signature to a user. Using an incognito browser window (or otherwise),
log in to the “jenkins” user account you created earlier. Download a client bundle for
this user. It is also recommended to change the description associated with the public
key stored in UCP such that you can identify in the future which key is being used for
signing.</p>
<p>Each time a user retrieves a new client bundle, a new keypair is generated. It is therefore
necessary to keep track of a specific bundle that a user chooses to designate as their signing bundle.</p>
<p>Once you have decompressed the client bundle, the only two files you need for the purposes
of signing are <code class="highlighter-rouge">cert.pem</code> and <code class="highlighter-rouge">key.pem</code>. These represent the public and private parts of
the users signing identity respectively. We will load the <code class="highlighter-rouge">key.pem</code> file onto the Jenkins
servers, and use <code class="highlighter-rouge">cert.pem</code> to create delegations for the “jenkins” user in our
Trusted Collection.</p>
<h2 id="prepare-the-jenkins-server">Prepare the Jenkins server</h2>
<h3 id="load-keypem-on-jenkins">Load <code class="highlighter-rouge">key.pem</code> on Jenkins</h3>
<p>You will need to use the notary client to load keys onto your Jenkins server. Simply run
<code class="highlighter-rouge">notary -d /path/to/.docker/trust key import /path/to/key.pem</code>. You will be asked to set
a password to encrypt the key on disk. For automated signing, this password can be configured
into the environment under the variable name <code class="highlighter-rouge">DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE</code>. The <code class="highlighter-rouge">-d</code>
flag to the command specifies the path to the <code class="highlighter-rouge">trust</code> subdirectory within the servers <code class="highlighter-rouge">docker</code>
configuration directory. Typically this is found at <code class="highlighter-rouge">~/.docker/trust</code>.</p>
<h3 id="enable-content-trust">Enable content trust</h3>
<p>There are two ways to enable content trust: globally, and per operation. To enabled content
trust globally, set the environment variable <code class="highlighter-rouge">DOCKER_CONTENT_TRUST=1</code>. To enable on a per
operation basis, wherever you run <code class="highlighter-rouge">docker image push</code> in your Jenkins scripts, add the flag
<code class="highlighter-rouge">--disable-content-trust=false</code>. You may wish to use this second option if you only want
to sign some images.</p>
<p>The Jenkins server is now prepared to sign images, but we need to create delegations referencing
the key to give it the necessary permissions.</p>
<h2 id="initialize-a-repository">Initialize a repository</h2>
<p>Any commands displayed in this section should <em>not</em> be run from the Jenkins server. You
will most likely want to run them from your local system.</p>
<p>If this is a new repository, create it in Docker Trusted Registry (DTR) or Docker Hub,
depending on which you use to store your images, before proceeding further.</p>
<p>We will now initialize the trust data and create the delegation that provides the Jenkins
key with permissions to sign content. The following commands initialize the trust data and
rotate snapshotting responsibilities to the server. This is necessary to ensure human involvement
is not required to publish new content.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>notary -s https://my_notary_server.com -d ~/.docker/trust init my_repository
notary -s https://my_notary_server.com -d ~/.docker/trust key rotate my_repository snapshot -r
notary -s https://my_notary_server.com -d ~/.docker/trust publish my_repository
</code></pre></div></div>
<p>The <code class="highlighter-rouge">-s</code> flag specifies the server hosting a notary service. If you are operating against
Docker Hub, this will be <code class="highlighter-rouge">https://notary.docker.io</code>. If you are operating against your own DTR
instance, this will be the same hostname you use in image names when running docker commands preceded
by the <code class="highlighter-rouge">https://</code> scheme. For example, if you would run <code class="highlighter-rouge">docker image push my_dtr:4443/me/an_image</code> the value
of the <code class="highlighter-rouge">-s</code> flag would be expected to be <code class="highlighter-rouge">https://my_dtr:4443</code>.</p>
<p>If you are using DTR, the name of the repository should be identical to the full name you use
in a <code class="highlighter-rouge">docker image push</code> command. If however you use Docker Hub, the name you use in a <code class="highlighter-rouge">docker image push</code>
must be preceded by <code class="highlighter-rouge">docker.io/</code>. i.e. if you ran <code class="highlighter-rouge">docker image push me/alpine</code>, you would
<code class="highlighter-rouge">notary init docker.io/me/alpine</code>.</p>
<p>For brevity, we will exclude the <code class="highlighter-rouge">-s</code> and <code class="highlighter-rouge">-d</code> flags from subsequent command, but be aware you
will still need to provide them for the commands to work correctly.</p>
<p>Now that the repository is initialized, we need to create the delegations for Jenkins. Docker
Content Trust treats a delegation role called <code class="highlighter-rouge">targets/releases</code> specially. It considers this
delegation to contain the canonical list of published images for the repository. It is therefore
generally desirable to add all users to this delegation with the following command:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>notary delegation add my_repository targets/releases --all-paths /path/to/cert.pem
</code></pre></div></div>
<p>This solves a number of prioritization problems that would result from needing to determine
which delegation should ultimately be trusted for a specific image. However, because it
is anticipated that any user will be able to sign the <code class="highlighter-rouge">targets/releases</code> role it is not trusted
in determining if a signing policy has been met. Therefore it is also necessary to create a
delegation specifically for Jenkins:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>notary delegation add my_repository targets/jenkins --all-paths /path/to/cert.pem
</code></pre></div></div>
<p>We will then publish both these updates (remember to add the correct <code class="highlighter-rouge">-s</code> and <code class="highlighter-rouge">-d</code> flags):</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>notary publish my_repository
</code></pre></div></div>
<p>Informational (Advanced): If we included the <code class="highlighter-rouge">targets/releases</code> role in determining if a signing policy
had been met, we would run into the situation of images being opportunistically deployed when
an appropriate user signs. In the scenario we have described so far, only images signed by
the “CI” team (containing only the “jenkins” user) should be deployable. If a user “Moby” could
also sign images but was not part of the “CI” team, they might sign and publish a new <code class="highlighter-rouge">targets/releases</code>
that contained their image. UCP would refuse to deploy this image because it was not signed
by the “CI” team. However, the next time Jenkins published an image, it would update and sign
the <code class="highlighter-rouge">targets/releases</code> role as whole, enabling “Moby” to deploy their image.</p>
<h2 id="conclusion">Conclusion</h2>
<p>With the Trusted Collection initialized, and delegations created, the Jenkins server will
now use the key we imported to sign any images we push to this repository.</p>
<p>Through either the Docker CLI, or the UCP browser interface, we will find that any images
that do not meet our signing policy cannot be used. The signing policy we set up requires
that the “CI” team must have signed any image we attempt to <code class="highlighter-rouge">docker image pull</code>, <code class="highlighter-rouge">docker container run</code>,
or <code class="highlighter-rouge">docker service create</code>, and the only member of that team is the “jenkins” user. This
restricts us to only running images that were published by our Jenkins CI system.</p>