diff --git a/_data/toc.yaml b/_data/toc.yaml index f8194b2b64f..c5d002c5c33 100644 --- a/_data/toc.yaml +++ b/_data/toc.yaml @@ -287,6 +287,8 @@ toc: title: Manage nodes in a swarm - path: /engine/swarm/services/ title: Deploy services to a swarm + - path: /engine/swarm/secrets/ + title: Manage sensitive data with Docker secrets - path: /engine/swarm/networking/ title: Attach services to an overlay network - path: /engine/swarm/admin_guide/ diff --git a/engine/swarm/secrets.md b/engine/swarm/secrets.md new file mode 100644 index 00000000000..cfecbc737d6 --- /dev/null +++ b/engine/swarm/secrets.md @@ -0,0 +1,847 @@ +--- +title: Manage sensitive data with Docker secrets +description: How to securely store, retrieve, and use sensitive data with Docker services +keywords: swarm, secrets, credentials, sensitive strings, sensitive data, security, encryption, encryption at rest +--- + +## About secrets + +In terms of Docker Swarm services, a _secret_ 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. In Docker 1.13 and higher, you can use Docker +_secrets_ 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. + +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: + +- Usernames and passwords +- TLS certificates and keys +- SSH keys +- Other important data such as the name of a database or internal server +- Generic strings or binary content (up to 500 kb in size) + +> **Note**: 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 with a scale of 1. + +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 in order to function in all +three environments. + +## How Docker manages secrets + +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. + +>**Warning**: Raft data is encrypted in Docker 1.13 and higher. If any of your +Swarm managers run an earlier version, and one of those managers becomes the +manager of the swarm, the secrets will be stored unencrypted in that node's Raft +logs. Before adding any secrets, update all of your manager nodes to Docker 1.13 +to prevent secrets from being written to plain-text Raft logs. + +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 at +`/run/secrets/`. You can update a service to grant it access to +additional secrets or revoke its access to a given secret at any time. + +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. + +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. + +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 [Rotate a secret](secrets.md#example-rotate-a-secret) for a way to +remove a secret without disrupting running services. + +In order 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. + +## Read more about `docker secret` commands + +Use these links to read about specific commands, or continue to the +[example about using secrets with a service](secrets.md#example-use-secrets-with-a-service). + +- [`docker secret create`](../reference/commandline/secret_create.md) +- [`docker secret inspect`](../reference/commandline/secret_inspect.md) +- [`docker service ls`](../reference/commandline/secret_ls.md) +- [`docker secret rm`](../reference/commandline/secret_rm.md) +- [`--secret`](../reference/commandline/service_create.md#create-a-service-with-secrets) flag for `docker service create` +- [`--secret-add` and `--secret-rm`](../reference/commandline/service_update.md#adding-and-removing-secrets) flags for `docker service update` + +## Examples + +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 +[Build support for Docker Secrets into your images](#build-support-for-docker-secrets-into-your-images). + +> **Note**: These examples use a single-Engine swarm and unscaled services for +> simplicity. + +### Simple example: Get started with secrets + +This simple example shows how secrets work in just a few commands. For a +real-world example, continue to +[Intermediate example: Use secrets with a Nginx service](#intermediate-example-use-secrets-with-a-nginx-service). + +1. Add a secret to Docker. The `docker secret create` command reads standard + input because the `-f` flag is set to `-`. + + ```bash + $ echo "This is a secret" | docker secret create my_secret_data -f - + ``` + +2. Create a `redis` service and grant it access to the secret. By default, + the container can access the secret at `/run/secrets/`, but + you can customize the file name on the container using the `target` option. + + ```bash + $ docker service create --name="redis" --secret="my_secret_data" redis:alpine + ``` + +3. Verify that the task is running without issues using `docker service ps`. If + everything is working, the output looks similar to this: + + ```bash + $ 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 + ``` + + If there were an error, and the task were failing and repeatedly restarting, + you would see something like this: + + ```bash + $ 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)" + ``` + +4. Get the ID of the `redis` service task container using `docker ps` , so that + you can use `docker exec` 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. + + ```bash + $ docker ps --filter name=redis -q + + 5cb1c2348a59 + + $ docker 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 exec $(docker ps --filter name=redis -q) cat /run/secrets/my_secret_data + + This is a secret + ``` + +5. Verify that the secret is **not** available if you commit the container. + + ```bash + $ 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 + ``` + +6. Try removing the secret. The removal fails because the `redis` is running + and has access to the secret. + + ```bash + + $ 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 + ``` + +7. Remove access to the secret from the running `redis` service by updating the + service. + + ```bash + $ docker service update --secret-rm="my_secret_data" redis + ``` + +8. Repeat steps 3 and 4 again, verifying that the service no longer has access + to the secret. The container ID will be different, because the + `service update` command redeploys the service. + + ```bash + $ docker 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 + ``` + +7. Stop and remove the service, and remove the secret from Docker. + + ```bash + $ docker service rm redis + + $ docker secret rm my_secret_data + ``` + +### Intermediate example: Use secrets with a Nginx service + +This example is divided into two parts. +[The first part](#generate-the-site-certificate) is all about generating +the site certificate and does not directly involve Docker secrets at all, but +it sets up [the second part](#configure-the-nginx-container), where you store +and use the site certificate and Nginx configuration as secrets. + +#### Generate the site certificate + +Generate a root CA and TLS certificate and key for your site. For production +sites, you may want to use a service such as `Let’s Encrypt` 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 [use Let's Encrypt](https://letsencrypt.org/getting-started/) to +generate the site key and certificate, name the files `site.key` and +`site.crt`, and skip to +[Configure the Nginx container](#configure-the-nginx-container). + +1. Generate a root key. + + ```bash + $ openssl genrsa -out "root-ca.key" 4096 + ``` + +2. Generate a CSR using the root key. + + ```bash + $ 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' + ``` + +3. Configure the root CA. Edit a new file called `root-ca.cnf` and paste + the following contents into it. This constrains the root CA to only be + able to sign leaf certificates and not intermediate CAs. + + ```none + [root_ca] + basicConstraints = critical,CA:TRUE,pathlen:1 + keyUsage = critical, nonRepudiation, cRLSign, keyCertSign + subjectKeyIdentifier=hash + ``` + +4. Sign the certificate. + + ```bash + $ 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 + ``` + +5. Generate the site key. + + ```bash + $ openssl genrsa -out "site.key" 4096 + ``` + +6. Generate the site certificate and sign it with the site key. + + ```bash + $ openssl req -new -key "site.key" -out "site.csr" -sha256 \ + -subj '/C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost' + ``` + +7. Configure the site certificate. Edit a new file called `site.cnf` 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. + + ```none + [server] + authorityKeyIdentifier=keyid,issuer + basicConstraints = critical,CA:FALSE + extendedKeyUsage=serverAuth + keyUsage = critical, digitalSignature, keyEncipherment + subjectAltName = DNS:localhost, IP:127.0.0.1 + subjectKeyIdentifier=hash + ``` + +8. Sign the site certificate. + + ```bash + $ 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 + ``` + +9. The `site.csr` and `site.cnf` files are not needed by the Nginx service, but + you will need them if you want to generate a new site certificate. Protect + the `root-ca.key` file. + +#### Configure the Nginx container + +1. Produce a very basic Nginx configuration that serves static files over HTTPS. + The TLS certificate and key will be stored as Docker secrets so that they + can be rotated easily. + + In the current directory, create a new file called `site.conf` with the + following contents: + + ```none + 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; + } + } + ``` + +2. Create three secrets, representing the key, the certificate, and the + `site.conf`. 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 will use them. In each of these + commands, the `-f` flag takes the path to the file containing the secret + on the host machine's filesystem. In these examples, the secret name and + the file name are the same. + + ```bash + $ docker secret create site.key -f site.key + + $ docker secret create site.crt -f site.crt + + $ docker secret create site.conf -f site.conf + ``` + + ```bash + $ 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 + ``` + +4. Create a service that runs Nginx and has access to the three secrets. The + last part of the `docker service create` command creates a symbolic link + from the location of the `site.conf` secret to `/etc/nginx.conf.d/`, 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. + + > **Note**: Normally you would create a Dockerfile which copies the `site.conf` + > into place, build the image, and run a container using your custom image. + > This example does not require a custom image. It puts the `site.conf` + > into place and runs the container all in one step. + + ```bash + $ docker service create \ + --name nginx \ + --secret site.key \ + --secret site.crt \ + --secret site.conf \ + --publish 3000:443 \ + nginx:latest \ + sh -c "ln -s /run/secrets/site.conf /etc/nginx/conf.d/site.conf && exec nginx -g 'daemon off;'" + ``` + + This uses the short syntax for the `--secret` flag, which creates files in + `/run/secrets/` with the same name as the secret. Within the running + containers, the following three files now exist: + + - `/run/secrets/site.key` + - `/run/secrets/site.crt` + - `/run/secrets/site.conf` + +5. Verify that the Nginx service is running. + + ```bash + $ 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 + ``` + +6. Verify that the service is operational: you can reach the Nginx + server, and that the correct TLS certificate is being used. + + ```bash + $ curl --cacert root-ca.crt https://0.0.0.0:3000 + + + + + Welcome to nginx! + + + +

Welcome to nginx!

+

If you see this page, the nginx web server is successfully installed and + working. Further configuration is required.

+ +

For online documentation and support please refer to + nginx.org.
+ Commercial support is available at + nginx.com.

+ +

Thank you for using nginx.

+ + + ``` + + ```bash + $ openssl s_client -connect 0.0.0.0: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) + ``` + +7. To clean up after running this example, remove the `nginx` service and the + stored secrets. + + ```bash + $ docker service rm nginx + + $ docker secret rm site.crt site.key nginx.conf + ``` + +### Advanced example: Use secrets with a WordPress service + +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 +[next example](#example-rotate-a-secret) 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. + +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. + +> **Note**: 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. +> +> Also, changing a MySQL root passphrase isn’t as simple as changing +> a file on disk. You must use a query or a `mysqladmin` command to change the +> password in MySQL. + +1. Generate a random alphanumeric password for MySQL and store it as a Docker + secret with the name `mysql_password` using the `docker secret create` + command. To make the password shorter or longer, adjust the last argument of + the `openssl` command. This is just one way to create a relatively random + password. You can use another command to generate the password if you + choose. + + > **Note**: 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 `docker service update`. 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. + + The `-f` flag is set to `-`, which indicates that the input is read from + STDIN. + + ```bash + $ openssl rand -base64 20 | docker secret create mysql_password -f - + + l1vinzevzhj4goakjap5ya409 + ``` + + The value returned is not the password, but the ID of the secret. In the + remainder of this tutorial, the ID output is omitted. + + Generate a second secret for the MySQL `root` user. This secret won't be + shared with the WordPress service created later. It's only needed to + bootstrap the `mysql` service. + + ```bash + $ openssl rand -base64 20 | docker secret create mysql_root_password -f - + ``` + + List the secrets managed by Docker using `docker secret ls`: + + ```bash + $ 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 + ``` + + The secrets are stored in the encrypted Raft logs for the swarm. + +2. Create a user-defined overlay network which will be used for communication + between the MySQL and WordPress services. There is no need to expose the + MySQL service to any external host or container. + + ```bash + $ docker network create -d overlay mysql_private + ``` + +3. Create the MySQL service. The MySQL service will have the following + characteristics: + + - Because the scale is set to `1`, 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. + - Only reachable by other containers on the `mysql_private` network. + - Uses the volume `mydata` to store the MySQL data, so that it persists + across restarts to the `mysql` service. + - The secrets are each mounted in a `tmpfs` filesystem at + `/run/secrets/mysql_password` and `/run/secrets/mysql_root_password`. + They are never exposed as environment variables, nor can they be committed + to an image if the `docker commit` command is run. The `mysql_password` + secret is the one used the non-privileged WordPress container will use to + connect to MySQL. + - Sets the environment variables `MYSQL_PASSWORD_FILE` and + `MYSQL_ROOT_PASSWORD_FILE` to point to the + files `/run/secrets/mysql_password` and `/run/secrets/mysql_root_password`. + The `mysql` 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. + - Sets environment variables `MYSQL_USER` and `MYSQL_DATABASE`. A new + database called `wordpress` is created when the container starts, and the + `wordpress` user will have full permissions for this database only. This + user will not be able to create or drop databases or change the MySQL + configuration. + + ```bash + $ 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 + ``` + +4. Verify that the `mysql` container is running using the `docker service ls` command. + + ```bash + $ docker service ls + + ID NAME MODE REPLICAS IMAGE + wvnh0siktqr3 mysql replicated 1/1 mysql:latest + ``` + + At this point, you could actually revoke the `mysql` service's access to the + `mysql_password` and `mysql_root_password` secrets because the passwords + have been saved in the MySQL system database. Don't do that for now, because + we will use them later to facilitate rotating the MySQL password. + +5. Now that MySQL is set up, create a WordPress service that connects to the + MySQL service. The WordPress service has the following characteristics: + + - Because the scale is set to `1`, 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. + - 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. + - Connects to the `mysql_private` network so it can communicate with the + `mysql` container, and also publishes port 80 to port 30000 on all swarm + nodes. + - Has access to the `mysql_password` secret, but specifies a different + target file name within the container. The WordPress container will use + the mount point `/run/secrets/wp_db_password`. Also specifies that the + secret is not group-or-world-readable, by setting the mode to + `0400`. + - Sets the environment variable `WORDPRESS_DB_PASSWORD_FILE` to the file + path where the secret is mounted. The WordPress service will read the + MySQL password string from that file and add it to the `wp-config.php` + configuration file. + - Connects to the MySQL container using the username `wordpress` and the + password in `/run/secrets/wp_db_password` and creates the `wordpress` + database if it does not yet exist. + - Stores its data, such as themes and plugins, in a volume called `wpdata` + so these files persist when the service restarts. + + ```bash + $ docker service create \ + --name wordpress \ + --replicas 1 \ + --network mysql_private \ + --publish 30000: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 + ``` + +6. Verify the service is running using `docker service ls` and + `docker service ps` commands. + + ```bash + $ docker service ls + + ID NAME MODE REPLICAS IMAGE + wvnh0siktqr3 mysql replicated 1/1 mysql:latest + nzt5xzae4n62 wordpress replicated 1/1 wordpress:latest + ``` + + ```bash + $ 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 + ``` + + At this point, you could actually revoke the WordPress service's access to + the `mysql_password` secret, because WordPress has copied the secret to its + configuration file `wp-config.php`. Don't do that for now, because we will + use it later to facilitate rotating the MySQL password. + +7. Access `http://localhost:30000/` from any swarm node and set up WordPress + using the web-based wizard. All of these settings are stored in the MySQL + `wordpress` 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 will need it to log into WordPress after + [rotating the secret](#example-rotate-a-secret). + + 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. + +8. 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. + + +### Example: Rotate a secret + +This example builds upon the previous one. In this scenario, you create a new +secret with a new MySQL password, update the `mysql` and `wordpress` services to +use it, then remove the old secret. + +**Note**: 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. + +1. Create the new password and store it as a secret named `mysql_password_v2`. + + ```bash + $ openssl rand -base64 20 | docker secret create mysql_password_v2 -f - + ``` + +2. 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. + + ```bash + $ 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 + ``` + + Updating a service causes it to restart, and when the MySQL service restarts + the second time, it has access to the old secret under + `/run/secrets/old_mysql_password` and the new secret under + `/run/secrets/mysql_password`. + + 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. + + > **Note**: This example does not rottate the MySQL `root` password. + +3. Now, change the MySQL password for the `wordpress` user using the + `mysqladmin` CLI. This command reads the old and new password from the files + in `/run/secrets` but does not expose them on the command line or save them + in the shell history. + + Do this quickly and move on to the next step, because WordPress will lose + the ability to connect to MySQL. + + First, find the ID of the `mysql` container task. + + ```bash + $ docker ps --filter --name=mysql -q + + c7705cf6176f + ``` + + Substitute the ID in the command below, or use the second variant which + uses shell expansion to do it all in a single step. + + ```bash + $ docker exec \ + bash -c 'mysqladmin --user=wordpress --password="$(< /run/secrets/old_mysql_password)" password "$(< /run/secrets/mysql_password)"' + ``` + + **or**: + + ```bash + $ docker exec $(docker ps --filter --name=mysql -q) \ + bash -c 'mysqladmin --user=wordpress --password="$(< /run/secrets/old_mysql_password)" password "$(< /run/secrets/mysql_password)"' + ``` + +4. Update the `wordpress` service to use the new password, keeping the target + path at `/run/secrets/wp_db_secret` and keeping the file permissions at + `0400`. This will trigger a rolling restart of the WordPress service and + the new secret will be used. + + ```bash + $ docker service update \ + --secret-rm mysql_password \ + --secret-add source=mysql_password_v2,target=wp_db_password,mode=0400 \ + wordpress + ``` + +5. Verify that WordPress works by browsing to http://localhost:30000/ on any + swarm node again. You'll need to use the WordPress username and password + from when you ran through the WordPress wizard in the previous task. + + Verify that the blog post you wrote still exists, and if you changed any + configuration values, verify that they are still changed. + +6. Revoke access to the old secret from the MySQL service and + remove the old secret from Docker. + + ```bash + + $ docker service update \ + --secret-rm mysql_password \ + mysql + + $ docker secret rm mysql_password + ``` + + +7. 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 `mydata` and `wpdata` volumes, + and the Docker secrets. + + ```bash + $ docker service rm wordpress mysql + + $ docker volume rm mydata wpdata + + $ docker secret rm mysql_password_v2 mysql_root_password + ``` + +## Build support for Docker Secrets into your images + +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. + +Many of the official images in the +[Docker library](https://github.com/docker-library/), such as the +[wordpress](https://github.com/docker-library/wordpress/) +image used in the above examples, have been updated in this way. + +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 `WORDPRESS_DB_PASSWORD`, also have variants which can read +their values from a file (`WORDPRESS_DB_PASSWORD_FILE`). 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. + +>**Note**: 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 `--link`). diff --git a/engine/swarm/services.md b/engine/swarm/services.md index 684fbe4deee..a7543c85fc2 100644 --- a/engine/swarm/services.md +++ b/engine/swarm/services.md @@ -70,6 +70,12 @@ $ docker service create --name helloworld alpine ping docker.com 9uk4639qpg7npwf3fn2aasksr ``` +## Grant a service access to secrets + +To create a service with access to Docker-managed secrets, use the `--secret` +flag. For more information, see +[Manage sensitive strings (secrets) for Docker services](secrets.md) + ### Specify the image version the service should use When you create a service without specifying any details about the version of