diff options
| author | Craig Jennings <c@cjennings.net> | 2024-04-07 13:41:34 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2024-04-07 13:41:34 -0500 |
| commit | 754bbf7a25a8dda49b5d08ef0d0443bbf5af0e36 (patch) | |
| tree | f1190704f78f04a2b0b4c977d20fe96a828377f1 /devdocs/docker/engine%2Fswarm%2Fsecrets%2Findex.html | |
new repository
Diffstat (limited to 'devdocs/docker/engine%2Fswarm%2Fsecrets%2Findex.html')
| -rw-r--r-- | devdocs/docker/engine%2Fswarm%2Fsecrets%2Findex.html | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/devdocs/docker/engine%2Fswarm%2Fsecrets%2Findex.html b/devdocs/docker/engine%2Fswarm%2Fsecrets%2Findex.html new file mode 100644 index 00000000..d91a6558 --- /dev/null +++ b/devdocs/docker/engine%2Fswarm%2Fsecrets%2Findex.html @@ -0,0 +1,333 @@ +<h1>Manage sensitive data with Docker secrets</h1> + +<h2 id="about-secrets">About secrets</h2> <p>In terms of Docker Swarm services, a <em>secret</em> is a blob of data, such as a password, SSH private key, SSL certificate, or another piece of data that should not be transmitted over a network or stored unencrypted in a Dockerfile or in your application’s source code. You can use Docker <em>secrets</em> to centrally manage this data and securely transmit it to only those containers that need access to it. Secrets are encrypted during transit and at rest in a Docker swarm. A given secret is only accessible to those services which have been granted explicit access to it, and only while those service tasks are running.</p> <p>You can use secrets to manage any sensitive data which a container needs at runtime but you don’t want to store in the image or in source control, such as:</p> <ul> <li>Usernames and passwords</li> <li>TLS certificates and keys</li> <li>SSH keys</li> <li>Other important data such as the name of a database or internal server</li> <li>Generic strings or binary content (up to 500 kb in size)</li> </ul> <blockquote> <p><strong>Note</strong>: Docker secrets are only available to swarm services, not to standalone containers. To use this feature, consider adapting your container to run as a service. Stateful containers can typically run with a scale of 1 without changing the container code.</p> </blockquote> <p>Another use case for using secrets is to provide a layer of abstraction between the container and a set of credentials. Consider a scenario where you have separate development, test, and production environments for your application. Each of these environments can have different credentials, stored in the development, test, and production swarms with the same secret name. Your containers only need to know the name of the secret to function in all three environments.</p> <p>You can also use secrets to manage non-sensitive data, such as configuration files. However, Docker supports the use of <a href="../configs/index">configs</a> for storing non-sensitive data. Configs are mounted into the container’s filesystem directly, without the use of a RAM disk.</p> <h3 id="windows-support">Windows support</h3> <p>Docker includes support for secrets on Windows containers. Where there are differences in the implementations, they are called out in the examples below. Keep the following notable differences in mind:</p> <ul> <li> <p>Microsoft Windows has no built-in driver for managing RAM disks, so within running Windows containers, secrets <strong>are</strong> persisted in clear text to the container’s root disk. However, the secrets are explicitly removed when a container stops. In addition, Windows does not support persisting a running container as an image using <code class="language-plaintext highlighter-rouge">docker commit</code> or similar commands.</p> </li> <li> <p>On Windows, we recommend enabling <a href="https://technet.microsoft.com/en-us/library/cc732774(v=ws.11).aspx">BitLocker</a> on the volume containing the Docker root directory on the host machine to ensure that secrets for running containers are encrypted at rest.</p> </li> <li> <p>Secret files with custom targets are not directly bind-mounted into Windows containers, since Windows does not support non-directory file bind-mounts. Instead, secrets for a container are all mounted in <code class="language-plaintext highlighter-rouge">C:\ProgramData\Docker\internal\secrets</code> (an implementation detail which should not be relied upon by applications) within the container. Symbolic links are used to point from there to the desired target of the secret within the container. The default target is <code class="language-plaintext highlighter-rouge">C:\ProgramData\Docker\secrets</code>.</p> </li> <li> <p>When creating a service which uses Windows containers, the options to specify UID, GID, and mode are not supported for secrets. Secrets are currently only accessible by administrators and users with <code class="language-plaintext highlighter-rouge">system</code> access within the container.</p> </li> </ul> <h2 id="how-docker-manages-secrets">How Docker manages secrets</h2> <p>When you add a secret to the swarm, Docker sends the secret to the swarm manager over a mutual TLS connection. The secret is stored in the Raft log, which is encrypted. The entire Raft log is replicated across the other managers, ensuring the same high availability guarantees for secrets as for the rest of the swarm management data.</p> <p>When you grant a newly-created or running service access to a secret, the decrypted secret is mounted into the container in an in-memory filesystem. The location of the mount point within the container defaults to <code class="language-plaintext highlighter-rouge">/run/secrets/<secret_name></code> in Linux containers, or <code class="language-plaintext highlighter-rouge">C:\ProgramData\Docker\secrets</code> in Windows containers. You can also specify a custom location.</p> <p>You can update a service to grant it access to additional secrets or revoke its access to a given secret at any time.</p> <p>A node only has access to (encrypted) secrets if the node is a swarm manager or if it is running service tasks which have been granted access to the secret. When a container task stops running, the decrypted secrets shared to it are unmounted from the in-memory filesystem for that container and flushed from the node’s memory.</p> <p>If a node loses connectivity to the swarm while it is running a task container with access to a secret, the task container still has access to its secrets, but cannot receive updates until the node reconnects to the swarm.</p> <p>You can add or inspect an individual secret at any time, or list all secrets. You cannot remove a secret that a running service is using. See <a href="index#example-rotate-a-secret">Rotate a secret</a> for a way to remove a secret without disrupting running services.</p> <p>To update or roll back secrets more easily, consider adding a version number or date to the secret name. This is made easier by the ability to control the mount point of the secret within a given container.</p> <h2 id="read-more-about-docker-secret-commands">Read more about <code class="language-plaintext highlighter-rouge">docker secret</code> commands</h2> <p>Use these links to read about specific commands, or continue to the <a href="index#simple-example-get-started-with-secrets">example about using secrets with a service</a>.</p> <ul> <li><a href="../../reference/commandline/secret_create/index"><code class="language-plaintext highlighter-rouge">docker secret create</code></a></li> <li><a href="../../reference/commandline/secret_inspect/index"><code class="language-plaintext highlighter-rouge">docker secret inspect</code></a></li> <li><a href="../../reference/commandline/secret_ls/index"><code class="language-plaintext highlighter-rouge">docker secret ls</code></a></li> <li><a href="../../reference/commandline/secret_rm/index"><code class="language-plaintext highlighter-rouge">docker secret rm</code></a></li> <li> +<a href="../../reference/commandline/service_create/index#create-a-service-with-secrets"><code class="language-plaintext highlighter-rouge">--secret</code></a> flag for <code class="language-plaintext highlighter-rouge">docker service create</code> +</li> <li> +<a href="../../reference/commandline/service_update/index#add-or-remove-secrets"><code class="language-plaintext highlighter-rouge">--secret-add</code> and <code class="language-plaintext highlighter-rouge">--secret-rm</code></a> flags for <code class="language-plaintext highlighter-rouge">docker service update</code> +</li> </ul> <h2 id="examples">Examples</h2> <p>This section includes three graduated examples which illustrate how to use Docker secrets. The images used in these examples have been updated to make it easier to use Docker secrets. To find out how to modify your own images in a similar way, see <a href="#build-support-for-docker-secrets-into-your-images">Build support for Docker Secrets into your images</a>.</p> <blockquote> <p><strong>Note</strong>: These examples use a single-Engine swarm and unscaled services for simplicity. The examples use Linux containers, but Windows containers also support secrets. See <a href="#windows-support">Windows support</a>.</p> </blockquote> <h3 id="defining-and-using-secrets-in-compose-files">Defining and using secrets in compose files</h3> <p>Both the <code class="language-plaintext highlighter-rouge">docker-compose</code> and <code class="language-plaintext highlighter-rouge">docker stack</code> commands support defining secrets in a compose file. See <a href="../../../compose/compose-file/compose-file-v3/index#secrets">the Compose file reference</a> for details.</p> <h3 id="simple-example-get-started-with-secrets">Simple example: Get started with secrets</h3> <p>This simple example shows how secrets work in just a few commands. For a real-world example, continue to <a href="#intermediate-example-use-secrets-with-a-nginx-service">Intermediate example: Use secrets with a Nginx service</a>.</p> <ol> <li> <p>Add a secret to Docker. The <code class="language-plaintext highlighter-rouge">docker secret create</code> command reads standard input because the last argument, which represents the file to read the secret from, is set to <code class="language-plaintext highlighter-rouge">-</code>.</p> <div class="highlight"><pre class="highlight" data-language="">$ printf "This is a secret" | docker secret create my_secret_data - +</pre></div> </li> <li> <p>Create a <code class="language-plaintext highlighter-rouge">redis</code> service and grant it access to the secret. By default, the container can access the secret at <code class="language-plaintext highlighter-rouge">/run/secrets/<secret_name></code>, but you can customize the file name on the container using the <code class="language-plaintext highlighter-rouge">target</code> option.</p> <div class="highlight"><pre class="highlight" data-language="">$ docker service create --name redis --secret my_secret_data redis:alpine +</pre></div> </li> <li> <p>Verify that the task is running without issues using <code class="language-plaintext highlighter-rouge">docker service ps</code>. If everything is working, the output looks similar to this:</p> <div class="highlight"><pre class="highlight" data-language="">$ docker service ps redis + +ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS +bkna6bpn8r1a redis.1 redis:alpine ip-172-31-46-109 Running Running 8 seconds ago +</pre></div> <p>If there were an error, and the task were failing and repeatedly restarting, you would see something like this:</p> <div class="highlight"><pre class="highlight" data-language="">$ docker service ps redis + +NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS +redis.1.siftice35gla redis:alpine moby Running Running 4 seconds ago + \_ redis.1.whum5b7gu13e redis:alpine moby Shutdown Failed 20 seconds ago "task: non-zero exit (1)" + \_ redis.1.2s6yorvd9zow redis:alpine moby Shutdown Failed 56 seconds ago "task: non-zero exit (1)" + \_ redis.1.ulfzrcyaf6pg redis:alpine moby Shutdown Failed about a minute ago "task: non-zero exit (1)" + \_ redis.1.wrny5v4xyps6 redis:alpine moby Shutdown Failed 2 minutes ago "task: non-zero exit (1)" +</pre></div> </li> <li> <p>Get the ID of the <code class="language-plaintext highlighter-rouge">redis</code> service task container using <code class="language-plaintext highlighter-rouge">docker ps</code> , so that you can use <code class="language-plaintext highlighter-rouge">docker container exec</code> to connect to the container and read the contents of the secret data file, which defaults to being readable by all and has the same name as the name of the secret. The first command below illustrates how to find the container ID, and the second and third commands use shell completion to do this automatically.</p> <div class="highlight"><pre class="highlight" data-language="">$ docker ps --filter name=redis -q + +5cb1c2348a59 + +$ docker container exec $(docker ps --filter name=redis -q) ls -l /run/secrets + +total 4 +-r--r--r-- 1 root root 17 Dec 13 22:48 my_secret_data + +$ docker container exec $(docker ps --filter name=redis -q) cat /run/secrets/my_secret_data + +This is a secret +</pre></div> </li> <li> <p>Verify that the secret is <strong>not</strong> available if you commit the container.</p> <pre>$ docker commit $(docker ps --filter name=redis -q) committed_redis + +$ docker run --rm -it committed_redis cat /run/secrets/my_secret_data + +cat: can't open '/run/secrets/my_secret_data': No such file or directory +</pre> </li> <li> <p>Try removing the secret. The removal fails because the <code class="language-plaintext highlighter-rouge">redis</code> service is running and has access to the secret.</p> <div class="highlight"><pre class="highlight" data-language="">$ docker secret ls + +ID NAME CREATED UPDATED +wwwrxza8sxy025bas86593fqs my_secret_data 4 hours ago 4 hours ago + + +$ docker secret rm my_secret_data + +Error response from daemon: rpc error: code = 3 desc = secret +'my_secret_data' is in use by the following service: redis +</pre></div> </li> <li> <p>Remove access to the secret from the running <code class="language-plaintext highlighter-rouge">redis</code> service by updating the service.</p> <div class="highlight"><pre class="highlight" data-language="">$ docker service update --secret-rm my_secret_data redis +</pre></div> </li> <li> <p>Repeat steps 3 and 4 again, verifying that the service no longer has access to the secret. The container ID is different, because the <code class="language-plaintext highlighter-rouge">service update</code> command redeploys the service.</p> <pre>$ docker container exec -it $(docker ps --filter name=redis -q) cat /run/secrets/my_secret_data + +cat: can't open '/run/secrets/my_secret_data': No such file or directory +</pre> </li> <li> <p>Stop and remove the service, and remove the secret from Docker.</p> <div class="highlight"><pre class="highlight" data-language="">$ docker service rm redis + +$ docker secret rm my_secret_data +</pre></div> </li> </ol> <h3 id="simple-example-use-secrets-in-a-windows-service">Simple example: Use secrets in a Windows service</h3> <p>This is a very simple example which shows how to use secrets with a Microsoft IIS service running on Docker for Windows running Windows containers on Microsoft Windows 10. It is a naive example that stores the webpage in a secret.</p> <p>This example assumes that you have PowerShell installed.</p> <ol> <li> <p>Save the following into a new file <code class="language-plaintext highlighter-rouge">index.html</code>.</p> <div class="highlight"><pre class="highlight" data-language=""><html lang="en"> + <head><title>Hello Docker</title></head> + <body> + <p>Hello Docker! You have deployed a HTML page.</p> + </body> +</html> +</pre></div> </li> <li> <p>If you have not already done so, initialize or join the swarm.</p> <div class="highlight"><pre class="highlight" data-language="">docker swarm init +</pre></div> </li> <li> <p>Save the <code class="language-plaintext highlighter-rouge">index.html</code> file as a swarm secret named <code class="language-plaintext highlighter-rouge">homepage</code>.</p> <div class="highlight"><pre class="highlight" data-language="">docker secret create homepage index.html +</pre></div> </li> <li> <p>Create an IIS service and grant it access to the <code class="language-plaintext highlighter-rouge">homepage</code> secret.</p> <div class="highlight"><pre class="highlight" data-language="">docker service create + --name my-iis + --publish published=8000,target=8000 + --secret src=homepage,target="\inetpub\wwwroot\index.html" + microsoft/iis:nanoserver +</pre></div> <blockquote> <p><strong>Note</strong>: There is technically no reason to use secrets for this example; <a href="../configs/index">configs</a> are a better fit. This example is for illustration only.</p> </blockquote> </li> <li> <p>Access the IIS service at <code class="language-plaintext highlighter-rouge">http://localhost:8000/</code>. It should serve the HTML content from the first step.</p> </li> <li> <p>Remove the service and the secret.</p> <div class="highlight"><pre class="highlight" data-language="">docker service rm my-iis +docker secret rm homepage +docker image remove secret-test +</pre></div> </li> </ol> <h3 id="intermediate-example-use-secrets-with-a-nginx-service">Intermediate example: Use secrets with a Nginx service</h3> <p>This example is divided into two parts. <a href="#generate-the-site-certificate">The first part</a> is all about generating the site certificate and does not directly involve Docker secrets at all, but it sets up <a href="#configure-the-nginx-container">the second part</a>, where you store and use the site certificate and Nginx configuration as secrets.</p> <h4 id="generate-the-site-certificate">Generate the site certificate</h4> <p>Generate a root CA and TLS certificate and key for your site. For production sites, you may want to use a service such as <code class="language-plaintext highlighter-rouge">Let’s Encrypt</code> to generate the TLS certificate and key, but this example uses command-line tools. This step is a little complicated, but is only a set-up step so that you have something to store as a Docker secret. If you want to skip these sub-steps, you can <a href="https://letsencrypt.org/getting-started/">use Let’s Encrypt</a> to generate the site key and certificate, name the files <code class="language-plaintext highlighter-rouge">site.key</code> and <code class="language-plaintext highlighter-rouge">site.crt</code>, and skip to <a href="#configure-the-nginx-container">Configure the Nginx container</a>.</p> <ol> <li> <p>Generate a root key.</p> <div class="highlight"><pre class="highlight" data-language="">$ openssl genrsa -out "root-ca.key" 4096 +</pre></div> </li> <li> <p>Generate a CSR using the root key.</p> <div class="highlight"><pre class="highlight" data-language="">$ openssl req \ + -new -key "root-ca.key" \ + -out "root-ca.csr" -sha256 \ + -subj '/C=US/ST=CA/L=San Francisco/O=Docker/CN=Swarm Secret Example CA' +</pre></div> </li> <li> <p>Configure the root CA. Edit a new file called <code class="language-plaintext highlighter-rouge">root-ca.cnf</code> and paste the following contents into it. This constrains the root CA to signing leaf certificates and not intermediate CAs.</p> <pre>[root_ca] +basicConstraints = critical,CA:TRUE,pathlen:1 +keyUsage = critical, nonRepudiation, cRLSign, keyCertSign +subjectKeyIdentifier=hash +</pre> </li> <li> <p>Sign the certificate.</p> <div class="highlight"><pre class="highlight" data-language="">$ openssl x509 -req -days 3650 -in "root-ca.csr" \ + -signkey "root-ca.key" -sha256 -out "root-ca.crt" \ + -extfile "root-ca.cnf" -extensions \ + root_ca +</pre></div> </li> <li> <p>Generate the site key.</p> <div class="highlight"><pre class="highlight" data-language="">$ openssl genrsa -out "site.key" 4096 +</pre></div> </li> <li> <p>Generate the site certificate and sign it with the site key.</p> <div class="highlight"><pre class="highlight" data-language="">$ openssl req -new -key "site.key" -out "site.csr" -sha256 \ + -subj '/C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost' +</pre></div> </li> <li> <p>Configure the site certificate. Edit a new file called <code class="language-plaintext highlighter-rouge">site.cnf</code> and paste the following contents into it. This constrains the site certificate so that it can only be used to authenticate a server and can’t be used to sign certificates.</p> <pre>[server] +authorityKeyIdentifier=keyid,issuer +basicConstraints = critical,CA:FALSE +extendedKeyUsage=serverAuth +keyUsage = critical, digitalSignature, keyEncipherment +subjectAltName = DNS:localhost, IP:127.0.0.1 +subjectKeyIdentifier=hash +</pre> </li> <li> <p>Sign the site certificate.</p> <div class="highlight"><pre class="highlight" data-language="">$ openssl x509 -req -days 750 -in "site.csr" -sha256 \ + -CA "root-ca.crt" -CAkey "root-ca.key" -CAcreateserial \ + -out "site.crt" -extfile "site.cnf" -extensions server +</pre></div> </li> <li> <p>The <code class="language-plaintext highlighter-rouge">site.csr</code> and <code class="language-plaintext highlighter-rouge">site.cnf</code> files are not needed by the Nginx service, but you need them if you want to generate a new site certificate. Protect the <code class="language-plaintext highlighter-rouge">root-ca.key</code> file.</p> </li> </ol> <h4 id="configure-the-nginx-container">Configure the Nginx container</h4> <ol> <li> <p>Produce a very basic Nginx configuration that serves static files over HTTPS. The TLS certificate and key are stored as Docker secrets so that they can be rotated easily.</p> <p>In the current directory, create a new file called <code class="language-plaintext highlighter-rouge">site.conf</code> with the following contents:</p> <pre>server { + listen 443 ssl; + server_name localhost; + ssl_certificate /run/secrets/site.crt; + ssl_certificate_key /run/secrets/site.key; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } +} +</pre> </li> <li> <p>Create three secrets, representing the key, the certificate, and the <code class="language-plaintext highlighter-rouge">site.conf</code>. You can store any file as a secret as long as it is smaller than 500 KB. This allows you to decouple the key, certificate, and configuration from the services that use them. In each of these commands, the last argument represents the path to the file to read the secret from on the host machine’s filesystem. In these examples, the secret name and the file name are the same.</p> <div class="highlight"><pre class="highlight" data-language="">$ docker secret create site.key site.key + +$ docker secret create site.crt site.crt + +$ docker secret create site.conf site.conf +</pre></div> <div class="highlight"><pre class="highlight" data-language="">$ docker secret ls + +ID NAME CREATED UPDATED +2hvoi9mnnaof7olr3z5g3g7fp site.key 58 seconds ago 58 seconds ago +aya1dh363719pkiuoldpter4b site.crt 24 seconds ago 24 seconds ago +zoa5df26f7vpcoz42qf2csth8 site.conf 11 seconds ago 11 seconds ago +</pre></div> </li> <li> <p>Create a service that runs Nginx and has access to the three secrets. The last part of the <code class="language-plaintext highlighter-rouge">docker service create</code> command creates a symbolic link from the location of the <code class="language-plaintext highlighter-rouge">site.conf</code> secret to <code class="language-plaintext highlighter-rouge">/etc/nginx.conf.d/</code>, where Nginx looks for extra configuration files. This step happens before Nginx actually starts, so you don’t need to rebuild your image if you change the Nginx configuration.</p> <blockquote> <p><strong>Note</strong>: Normally you would create a Dockerfile which copies the <code class="language-plaintext highlighter-rouge">site.conf</code> into place, build the image, and run a container using your custom image. This example does not require a custom image. It puts the <code class="language-plaintext highlighter-rouge">site.conf</code> into place and runs the container all in one step.</p> </blockquote> <p>Secrets are located within the <code class="language-plaintext highlighter-rouge">/run/secrets/</code> directory in the container by default, which may require extra steps in the container to make the secret available in a different path. The example below creates a symbolic link to the true location of the <code class="language-plaintext highlighter-rouge">site.conf</code> file so that Nginx can read it:</p> <div class="highlight"><pre class="highlight" data-language="">$ docker service create \ + --name nginx \ + --secret site.key \ + --secret site.crt \ + --secret site.conf \ + --publish published=3000,target=443 \ + nginx:latest \ + sh -c "ln -s /run/secrets/site.conf /etc/nginx/conf.d/site.conf && exec nginx -g 'daemon off;'" +</pre></div> <p>Instead of creating symlinks, secrets allow you to specify a custom location using the <code class="language-plaintext highlighter-rouge">target</code> option. The example below illustrates how the <code class="language-plaintext highlighter-rouge">site.conf</code> secret is made available at <code class="language-plaintext highlighter-rouge">/etc/nginx/conf.d/site.conf</code> inside the container without the use of symbolic links:</p> <div class="highlight"><pre class="highlight" data-language="">$ docker service create \ + --name nginx \ + --secret site.key \ + --secret site.crt \ + --secret source=site.conf,target=/etc/nginx/conf.d/site.conf \ + --publish published=3000,target=443 \ + nginx:latest \ + sh -c "exec nginx -g 'daemon off;'" +</pre></div> <p>The <code class="language-plaintext highlighter-rouge">site.key</code> and <code class="language-plaintext highlighter-rouge">site.crt</code> secrets use the short-hand syntax, without a custom <code class="language-plaintext highlighter-rouge">target</code> location set. The short syntax mounts the secrets in `/run/secrets/ with the same name as the secret. Within the running containers, the following three files now exist:</p> <ul> <li><code class="language-plaintext highlighter-rouge">/run/secrets/site.key</code></li> <li><code class="language-plaintext highlighter-rouge">/run/secrets/site.crt</code></li> <li><code class="language-plaintext highlighter-rouge">/etc/nginx/conf.d/site.conf</code></li> </ul> </li> <li> <p>Verify that the Nginx service is running.</p> <div class="highlight"><pre class="highlight" data-language="">$ docker service ls + +ID NAME MODE REPLICAS IMAGE +zeskcec62q24 nginx replicated 1/1 nginx:latest + +$ docker service ps nginx + +NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS +nginx.1.9ls3yo9ugcls nginx:latest moby Running Running 3 minutes ago +</pre></div> </li> <li> <p>Verify that the service is operational: you can reach the Nginx server, and that the correct TLS certificate is being used.</p> <div class="highlight"><pre class="highlight" data-language="">$ curl --cacert root-ca.crt https://localhost:3000 + +<!DOCTYPE html> +<html> +<head> +<title>Welcome to nginx!</title> +<style> + body { + width: 35em; + margin: 0 auto; + font-family: Tahoma, Verdana, Arial, sans-serif; + } +</style> +</head> +<body> +<h1>Welcome to nginx!</h1> +<p>If you see this page, the nginx web server is successfully installed and +working. Further configuration is required.</p> + +<p>For online documentation and support. refer to +<a href="https://nginx.org">nginx.org</a>.<br/> +Commercial support is available at +<a href="https://www.nginx.com">nginx.com</a>.</p> + +<p><em>Thank you for using nginx.</em></p> +</body> +</html> +</pre></div> <div class="highlight"><pre class="highlight" data-language="">$ openssl s_client -connect localhost:3000 -CAfile root-ca.crt + +CONNECTED(00000003) +depth=1 /C=US/ST=CA/L=San Francisco/O=Docker/CN=Swarm Secret Example CA +verify return:1 +depth=0 /C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost +verify return:1 +--- +Certificate chain + 0 s:/C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost + i:/C=US/ST=CA/L=San Francisco/O=Docker/CN=Swarm Secret Example CA +--- +Server certificate +-----BEGIN CERTIFICATE----- +… +-----END CERTIFICATE----- +subject=/C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost +issuer=/C=US/ST=CA/L=San Francisco/O=Docker/CN=Swarm Secret Example CA +--- +No client certificate CA names sent +--- +SSL handshake has read 1663 bytes and written 712 bytes +--- +New, TLSv1/SSLv3, Cipher is AES256-SHA +Server public key is 4096 bit +Secure Renegotiation IS supported +Compression: NONE +Expansion: NONE +SSL-Session: + Protocol : TLSv1 + Cipher : AES256-SHA + Session-ID: A1A8BF35549C5715648A12FD7B7E3D861539316B03440187D9DA6C2E48822853 + Session-ID-ctx: + Master-Key: F39D1B12274BA16D3A906F390A61438221E381952E9E1E05D3DD784F0135FB81353DA38C6D5C021CB926E844DFC49FC4 + Key-Arg : None + Start Time: 1481685096 + Timeout : 300 (sec) + Verify return code: 0 (ok) +</pre></div> </li> <li> <p>To clean up after running this example, remove the <code class="language-plaintext highlighter-rouge">nginx</code> service and the stored secrets.</p> <div class="highlight"><pre class="highlight" data-language="">$ docker service rm nginx + +$ docker secret rm site.crt site.key site.conf +</pre></div> </li> </ol> <h3 id="advanced-example-use-secrets-with-a-wordpress-service">Advanced example: Use secrets with a WordPress service</h3> <p>In this example, you create a single-node MySQL service with a custom root password, add the credentials as secrets, and create a single-node WordPress service which uses these credentials to connect to MySQL. The <a href="#example-rotate-a-secret">next example</a> builds on this one and shows you how to rotate the MySQL password and update the services so that the WordPress service can still connect to MySQL.</p> <p>This example illustrates some techniques to use Docker secrets to avoid saving sensitive credentials within your image or passing them directly on the command line.</p> <blockquote> <p><strong>Note</strong>: This example uses a single-Engine swarm for simplicity, and uses a single-node MySQL service because a single MySQL server instance cannot be scaled by simply using a replicated service, and setting up a MySQL cluster is beyond the scope of this example.</p> <p>Also, changing a MySQL root passphrase isn’t as simple as changing a file on disk. You must use a query or a <code class="language-plaintext highlighter-rouge">mysqladmin</code> command to change the password in MySQL.</p> </blockquote> <ol> <li> <p>Generate a random alphanumeric password for MySQL and store it as a Docker secret with the name <code class="language-plaintext highlighter-rouge">mysql_password</code> using the <code class="language-plaintext highlighter-rouge">docker secret create</code> command. To make the password shorter or longer, adjust the last argument of the <code class="language-plaintext highlighter-rouge">openssl</code> command. This is just one way to create a relatively random password. You can use another command to generate the password if you choose.</p> <blockquote> <p><strong>Note</strong>: After you create a secret, you cannot update it. You can only remove and re-create it, and you cannot remove a secret that a service is using. However, you can grant or revoke a running service’s access to secrets using <code class="language-plaintext highlighter-rouge">docker service update</code>. If you need the ability to update a secret, consider adding a version component to the secret name, so that you can later add a new version, update the service to use it, then remove the old version.</p> </blockquote> <p>The last argument is set to <code class="language-plaintext highlighter-rouge">-</code>, which indicates that the input is read from standard input.</p> <div class="highlight"><pre class="highlight" data-language="">$ openssl rand -base64 20 | docker secret create mysql_password - + +l1vinzevzhj4goakjap5ya409 +</pre></div> <p>The value returned is not the password, but the ID of the secret. In the remainder of this tutorial, the ID output is omitted.</p> <p>Generate a second secret for the MySQL <code class="language-plaintext highlighter-rouge">root</code> user. This secret isn’t shared with the WordPress service created later. It’s only needed to bootstrap the <code class="language-plaintext highlighter-rouge">mysql</code> service.</p> <div class="highlight"><pre class="highlight" data-language="">$ openssl rand -base64 20 | docker secret create mysql_root_password - +</pre></div> <p>List the secrets managed by Docker using <code class="language-plaintext highlighter-rouge">docker secret ls</code>:</p> <div class="highlight"><pre class="highlight" data-language="">$ docker secret ls + +ID NAME CREATED UPDATED +l1vinzevzhj4goakjap5ya409 mysql_password 41 seconds ago 41 seconds ago +yvsczlx9votfw3l0nz5rlidig mysql_root_password 12 seconds ago 12 seconds ago +</pre></div> <p>The secrets are stored in the encrypted Raft logs for the swarm.</p> </li> <li> <p>Create a user-defined overlay network which is used for communication between the MySQL and WordPress services. There is no need to expose the MySQL service to any external host or container.</p> <div class="highlight"><pre class="highlight" data-language="">$ docker network create -d overlay mysql_private +</pre></div> </li> <li> <p>Create the MySQL service. The MySQL service has the following characteristics:</p> <ul> <li>Because the scale is set to <code class="language-plaintext highlighter-rouge">1</code>, only a single MySQL task runs. Load-balancing MySQL is left as an exercise to the reader and involves more than just scaling the service.</li> <li>Only reachable by other containers on the <code class="language-plaintext highlighter-rouge">mysql_private</code> network.</li> <li>Uses the volume <code class="language-plaintext highlighter-rouge">mydata</code> to store the MySQL data, so that it persists across restarts to the <code class="language-plaintext highlighter-rouge">mysql</code> service.</li> <li>The secrets are each mounted in a <code class="language-plaintext highlighter-rouge">tmpfs</code> filesystem at <code class="language-plaintext highlighter-rouge">/run/secrets/mysql_password</code> and <code class="language-plaintext highlighter-rouge">/run/secrets/mysql_root_password</code>. They are never exposed as environment variables, nor can they be committed to an image if the <code class="language-plaintext highlighter-rouge">docker commit</code> command is run. The <code class="language-plaintext highlighter-rouge">mysql_password</code> secret is the one used by the non-privileged WordPress container to connect to MySQL.</li> <li>Sets the environment variables <code class="language-plaintext highlighter-rouge">MYSQL_PASSWORD_FILE</code> and <code class="language-plaintext highlighter-rouge">MYSQL_ROOT_PASSWORD_FILE</code> to point to the files <code class="language-plaintext highlighter-rouge">/run/secrets/mysql_password</code> and <code class="language-plaintext highlighter-rouge">/run/secrets/mysql_root_password</code>. The <code class="language-plaintext highlighter-rouge">mysql</code> image reads the password strings from those files when initializing the system database for the first time. Afterward, the passwords are stored in the MySQL system database itself.</li> <li> <p>Sets environment variables <code class="language-plaintext highlighter-rouge">MYSQL_USER</code> and <code class="language-plaintext highlighter-rouge">MYSQL_DATABASE</code>. A new database called <code class="language-plaintext highlighter-rouge">wordpress</code> is created when the container starts, and the <code class="language-plaintext highlighter-rouge">wordpress</code> user has full permissions for this database only. This user cannot create or drop databases or change the MySQL configuration.</p> <div class="highlight"><pre class="highlight" data-language="">$ docker service create \ + --name mysql \ + --replicas 1 \ + --network mysql_private \ + --mount type=volume,source=mydata,destination=/var/lib/mysql \ + --secret source=mysql_root_password,target=mysql_root_password \ + --secret source=mysql_password,target=mysql_password \ + -e MYSQL_ROOT_PASSWORD_FILE="/run/secrets/mysql_root_password" \ + -e MYSQL_PASSWORD_FILE="/run/secrets/mysql_password" \ + -e MYSQL_USER="wordpress" \ + -e MYSQL_DATABASE="wordpress" \ + mysql:latest +</pre></div> </li> </ul> </li> <li> <p>Verify that the <code class="language-plaintext highlighter-rouge">mysql</code> container is running using the <code class="language-plaintext highlighter-rouge">docker service ls</code> command.</p> <div class="highlight"><pre class="highlight" data-language="">$ docker service ls + +ID NAME MODE REPLICAS IMAGE +wvnh0siktqr3 mysql replicated 1/1 mysql:latest +</pre></div> <p>At this point, you could actually revoke the <code class="language-plaintext highlighter-rouge">mysql</code> service’s access to the <code class="language-plaintext highlighter-rouge">mysql_password</code> and <code class="language-plaintext highlighter-rouge">mysql_root_password</code> secrets because the passwords have been saved in the MySQL system database. Don’t do that for now, because we use them later to facilitate rotating the MySQL password.</p> </li> <li> <p>Now that MySQL is set up, create a WordPress service that connects to the MySQL service. The WordPress service has the following characteristics:</p> <ul> <li>Because the scale is set to <code class="language-plaintext highlighter-rouge">1</code>, only a single WordPress task runs. Load-balancing WordPress is left as an exercise to the reader, because of limitations with storing WordPress session data on the container filesystem.</li> <li>Exposes WordPress on port 30000 of the host machine, so that you can access it from external hosts. You can expose port 80 instead if you do not have a web server running on port 80 of the host machine.</li> <li>Connects to the <code class="language-plaintext highlighter-rouge">mysql_private</code> network so it can communicate with the <code class="language-plaintext highlighter-rouge">mysql</code> container, and also publishes port 80 to port 30000 on all swarm nodes.</li> <li>Has access to the <code class="language-plaintext highlighter-rouge">mysql_password</code> secret, but specifies a different target file name within the container. The WordPress container uses the mount point <code class="language-plaintext highlighter-rouge">/run/secrets/wp_db_password</code>. Also specifies that the secret is not group-or-world-readable, by setting the mode to <code class="language-plaintext highlighter-rouge">0400</code>.</li> <li>Sets the environment variable <code class="language-plaintext highlighter-rouge">WORDPRESS_DB_PASSWORD_FILE</code> to the file path where the secret is mounted. The WordPress service reads the MySQL password string from that file and add it to the <code class="language-plaintext highlighter-rouge">wp-config.php</code> configuration file.</li> <li>Connects to the MySQL container using the username <code class="language-plaintext highlighter-rouge">wordpress</code> and the password in <code class="language-plaintext highlighter-rouge">/run/secrets/wp_db_password</code> and creates the <code class="language-plaintext highlighter-rouge">wordpress</code> database if it does not yet exist.</li> <li>Stores its data, such as themes and plugins, in a volume called <code class="language-plaintext highlighter-rouge">wpdata</code> so these files persist when the service restarts.</li> </ul> <div class="highlight"><pre class="highlight" data-language="">$ docker service create \ + --name wordpress \ + --replicas 1 \ + --network mysql_private \ + --publish published=30000,target=80 \ + --mount type=volume,source=wpdata,destination=/var/www/html \ + --secret source=mysql_password,target=wp_db_password,mode=0400 \ + -e WORDPRESS_DB_USER="wordpress" \ + -e WORDPRESS_DB_PASSWORD_FILE="/run/secrets/wp_db_password" \ + -e WORDPRESS_DB_HOST="mysql:3306" \ + -e WORDPRESS_DB_NAME="wordpress" \ + wordpress:latest +</pre></div> </li> <li> <p>Verify the service is running using <code class="language-plaintext highlighter-rouge">docker service ls</code> and <code class="language-plaintext highlighter-rouge">docker service ps</code> commands.</p> <div class="highlight"><pre class="highlight" data-language="">$ docker service ls + +ID NAME MODE REPLICAS IMAGE +wvnh0siktqr3 mysql replicated 1/1 mysql:latest +nzt5xzae4n62 wordpress replicated 1/1 wordpress:latest +</pre></div> <div class="highlight"><pre class="highlight" data-language="">$ docker service ps wordpress + +ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS +aukx6hgs9gwc wordpress.1 wordpress:latest moby Running Running 52 seconds ago +</pre></div> <p>At this point, you could actually revoke the WordPress service’s access to the <code class="language-plaintext highlighter-rouge">mysql_password</code> secret, because WordPress has copied the secret to its configuration file <code class="language-plaintext highlighter-rouge">wp-config.php</code>. Don’t do that for now, because we use it later to facilitate rotating the MySQL password.</p> </li> <li> <p>Access <code class="language-plaintext highlighter-rouge">http://localhost:30000/</code> from any swarm node and set up WordPress using the web-based wizard. All of these settings are stored in the MySQL <code class="language-plaintext highlighter-rouge">wordpress</code> database. WordPress automatically generates a password for your WordPress user, which is completely different from the password WordPress uses to access MySQL. Store this password securely, such as in a password manager. You need it to log into WordPress after <a href="#example-rotate-a-secret">rotating the secret</a>.</p> <p>Go ahead and write a blog post or two and install a WordPress plugin or theme to verify that WordPress is fully operational and its state is saved across service restarts.</p> </li> <li> <p>Do not clean up any services or secrets if you intend to proceed to the next example, which demonstrates how to rotate the MySQL root password.</p> </li> </ol> <h3 id="example-rotate-a-secret">Example: Rotate a secret</h3> <p>This example builds upon the previous one. In this scenario, you create a new secret with a new MySQL password, update the <code class="language-plaintext highlighter-rouge">mysql</code> and <code class="language-plaintext highlighter-rouge">wordpress</code> services to use it, then remove the old secret.</p> <blockquote> <p><strong>Note</strong>: Changing the password on a MySQL database involves running extra queries or commands, as opposed to just changing a single environment variable or a file, since the image only sets the MySQL password if the database doesn’t already exist, and MySQL stores the password within a MySQL database by default. Rotating passwords or other secrets may involve additional steps outside of Docker.</p> </blockquote> <ol> <li> <p>Create the new password and store it as a secret named <code class="language-plaintext highlighter-rouge">mysql_password_v2</code>.</p> <div class="highlight"><pre class="highlight" data-language="">$ openssl rand -base64 20 | docker secret create mysql_password_v2 - +</pre></div> </li> <li> <p>Update the MySQL service to give it access to both the old and new secrets. Remember that you cannot update or rename a secret, but you can revoke a secret and grant access to it using a new target filename.</p> <div class="highlight"><pre class="highlight" data-language="">$ docker service update \ + --secret-rm mysql_password mysql + +$ docker service update \ + --secret-add source=mysql_password,target=old_mysql_password \ + --secret-add source=mysql_password_v2,target=mysql_password \ + mysql +</pre></div> <p>Updating a service causes it to restart, and when the MySQL service restarts the second time, it has access to the old secret under <code class="language-plaintext highlighter-rouge">/run/secrets/old_mysql_password</code> and the new secret under <code class="language-plaintext highlighter-rouge">/run/secrets/mysql_password</code>.</p> <p>Even though the MySQL service has access to both the old and new secrets now, the MySQL password for the WordPress user has not yet been changed.</p> <blockquote> <p><strong>Note</strong>: This example does not rotate the MySQL <code class="language-plaintext highlighter-rouge">root</code> password.</p> </blockquote> </li> <li> <p>Now, change the MySQL password for the <code class="language-plaintext highlighter-rouge">wordpress</code> user using the <code class="language-plaintext highlighter-rouge">mysqladmin</code> CLI. This command reads the old and new password from the files in <code class="language-plaintext highlighter-rouge">/run/secrets</code> but does not expose them on the command line or save them in the shell history.</p> <p>Do this quickly and move on to the next step, because WordPress loses the ability to connect to MySQL.</p> <p>First, find the ID of the <code class="language-plaintext highlighter-rouge">mysql</code> container task.</p> <div class="highlight"><pre class="highlight" data-language="">$ docker ps --filter name=mysql -q + +c7705cf6176f +</pre></div> <p>Substitute the ID in the command below, or use the second variant which uses shell expansion to do it all in a single step.</p> <div class="highlight"><pre class="highlight" data-language="">$ docker container exec <CONTAINER_ID> \ + bash -c 'mysqladmin --user=wordpress --password="$(< /run/secrets/old_mysql_password)" password "$(< /run/secrets/mysql_password)"' +</pre></div> <p><strong>or</strong>:</p> <div class="highlight"><pre class="highlight" data-language="">$ docker container exec $(docker ps --filter name=mysql -q) \ + bash -c 'mysqladmin --user=wordpress --password="$(< /run/secrets/old_mysql_password)" password "$(< /run/secrets/mysql_password)"' +</pre></div> </li> <li> <p>Update the <code class="language-plaintext highlighter-rouge">wordpress</code> service to use the new password, keeping the target path at <code class="language-plaintext highlighter-rouge">/run/secrets/wp_db_password</code> and keeping the file permissions at <code class="language-plaintext highlighter-rouge">0400</code>. This triggers a rolling restart of the WordPress service and the new secret is used.</p> <div class="highlight"><pre class="highlight" data-language="">$ docker service update \ + --secret-rm mysql_password \ + --secret-add source=mysql_password_v2,target=wp_db_password,mode=0400 \ + wordpress +</pre></div> </li> <li> <p>Verify that WordPress works by browsing to http://localhost:30000/ on any swarm node again. Use the WordPress username and password from when you ran through the WordPress wizard in the previous task.</p> <p>Verify that the blog post you wrote still exists, and if you changed any configuration values, verify that they are still changed.</p> </li> <li> <p>Revoke access to the old secret from the MySQL service and remove the old secret from Docker.</p> <div class="highlight"><pre class="highlight" data-language="">$ docker service update \ + --secret-rm mysql_password \ + mysql + +$ docker secret rm mysql_password +</pre></div> </li> <li> <p>If you want to try the running all of these examples again or just want to clean up after running through them, use these commands to remove the WordPress service, the MySQL container, the <code class="language-plaintext highlighter-rouge">mydata</code> and <code class="language-plaintext highlighter-rouge">wpdata</code> volumes, and the Docker secrets.</p> <div class="highlight"><pre class="highlight" data-language="">$ docker service rm wordpress mysql + +$ docker volume rm mydata wpdata + +$ docker secret rm mysql_password_v2 mysql_root_password +</pre></div> </li> </ol> <h2 id="build-support-for-docker-secrets-into-your-images">Build support for Docker Secrets into your images</h2> <p>If you develop a container that can be deployed as a service and requires sensitive data, such as a credential, as an environment variable, consider adapting your image to take advantage of Docker secrets. One way to do this is to ensure that each parameter you pass to the image when creating the container can also be read from a file.</p> <p>Many of the Docker Official Images in the <a href="https://github.com/docker-library/">Docker library</a>, such as the <a href="https://github.com/docker-library/wordpress/">wordpress</a> image used in the above examples, have been updated in this way.</p> <p>When you start a WordPress container, you provide it with the parameters it needs by setting them as environment variables. The WordPress image has been updated so that the environment variables which contain important data for WordPress, such as <code class="language-plaintext highlighter-rouge">WORDPRESS_DB_PASSWORD</code>, also have variants which can read their values from a file (<code class="language-plaintext highlighter-rouge">WORDPRESS_DB_PASSWORD_FILE</code>). This strategy ensures that backward compatibility is preserved, while allowing your container to read the information from a Docker-managed secret instead of being passed directly.</p> <blockquote> <p><strong>Note</strong></p> <p>Docker secrets do not set environment variables directly. This was a conscious decision, because environment variables can unintentionally be leaked between containers (for instance, if you use <code class="language-plaintext highlighter-rouge">--link</code>).</p> </blockquote> <h2 id="use-secrets-in-compose">Use Secrets in Compose</h2> <div class="highlight"><pre class="highlight" data-language="">version: "3.9" + +services: + db: + image: mysql:latest + volumes: + - db_data:/var/lib/mysql + environment: + MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password + MYSQL_DATABASE: wordpress + MYSQL_USER: wordpress + MYSQL_PASSWORD_FILE: /run/secrets/db_password + secrets: + - db_root_password + - db_password + + wordpress: + depends_on: + - db + image: wordpress:latest + ports: + - "8000:80" + environment: + WORDPRESS_DB_HOST: db:3306 + WORDPRESS_DB_USER: wordpress + WORDPRESS_DB_PASSWORD_FILE: /run/secrets/db_password + secrets: + - db_password + + +secrets: + db_password: + file: db_password.txt + db_root_password: + file: db_root_password.txt + +volumes: + db_data: +</pre></div> <p>This example creates a simple WordPress site using two secrets in a compose file.</p> <p>The keyword <code class="language-plaintext highlighter-rouge">secrets:</code> defines two secrets <code class="language-plaintext highlighter-rouge">db_password:</code> and <code class="language-plaintext highlighter-rouge">db_root_password:</code>.</p> <p>When deploying, Docker creates these two secrets and populates them with the content from the file specified in the compose file.</p> <p>The db service uses both secrets, and the wordpress is using one.</p> <p>When you deploy, Docker mounts a file under <code class="language-plaintext highlighter-rouge">/run/secrets/<secret_name></code> in the services. These files are never persisted in disk, but are managed in memory.</p> <p>Each service uses environment variables to specify where the service should look for that secret data.</p> <p>More information on short and long syntax for secrets can be found at <a href="../../../compose/compose-file/compose-file-v3/index#secrets">Compose file version 3 reference</a>.</p> +<p><a href="https://docs.docker.com/search/?q=swarm">swarm</a>, <a href="https://docs.docker.com/search/?q=secrets">secrets</a>, <a href="https://docs.docker.com/search/?q=credentials">credentials</a>, <a href="https://docs.docker.com/search/?q=sensitive%20strings">sensitive strings</a>, <a href="https://docs.docker.com/search/?q=sensitive%20data">sensitive data</a>, <a href="https://docs.docker.com/search/?q=security">security</a>, <a href="https://docs.docker.com/search/?q=encryption">encryption</a>, <a href="https://docs.docker.com/search/?q=encryption%20at%20rest">encryption at rest</a></p> +<div class="_attribution"> + <p class="_attribution-p"> + © 2019 Docker, Inc.<br>Licensed under the Apache License, Version 2.0.<br>Docker and the Docker logo are trademarks or registered trademarks of Docker, Inc. in the United States and/or other countries.<br>Docker, Inc. and other parties may also have trademark rights in other terms used herein.<br> + <a href="https://docs.docker.com/engine/swarm/secrets/" class="_attribution-link">https://docs.docker.com/engine/swarm/secrets/</a> + </p> +</div> |
