mirror of https://github.com/docker/docs.git
Document Docker secrets
Fixes #529 Signed-off-by: Misty Stanley-Jones <misty@docker.com>
This commit is contained in:
parent
5b2842922c
commit
f4a28b31ec
|
@ -289,6 +289,8 @@ toc:
|
||||||
title: Manage nodes in a swarm
|
title: Manage nodes in a swarm
|
||||||
- path: /engine/swarm/services/
|
- path: /engine/swarm/services/
|
||||||
title: Deploy services to a swarm
|
title: Deploy services to a swarm
|
||||||
|
- path: /engine/swarm/secrets/
|
||||||
|
title: Manage sensitive data with Docker secrets
|
||||||
- path: /engine/swarm/networking/
|
- path: /engine/swarm/networking/
|
||||||
title: Attach services to an overlay network
|
title: Attach services to an overlay network
|
||||||
- path: /engine/swarm/admin_guide/
|
- path: /engine/swarm/admin_guide/
|
||||||
|
|
|
@ -0,0 +1,837 @@
|
||||||
|
---
|
||||||
|
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/<secret_name>`. 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.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ echo "This is a secret" | docker secret create my_secret_data
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create a `redis` service and grant it access to the secret. By default,
|
||||||
|
the container can access the secret at `/run/secrets/<secret_name>`, 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.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cat site.key | docker secret create site.key
|
||||||
|
|
||||||
|
$ cat site.crt | docker secret create site.crt
|
||||||
|
|
||||||
|
$ cat site.conf | docker create 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
|
||||||
|
|
||||||
|
<!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 please refer to
|
||||||
|
<a href="http://nginx.org/">nginx.org</a>.<br/>
|
||||||
|
Commercial support is available at
|
||||||
|
<a href="http://nginx.com/">nginx.com</a>.</p>
|
||||||
|
|
||||||
|
<p><em>Thank you for using nginx.</em></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
```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.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ openssl rand -base64 20 | docker secret create mysql_password
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
- 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 will often 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
|
||||||
|
```
|
||||||
|
|
||||||
|
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 root password has not yet been changed.
|
||||||
|
|
||||||
|
3. Now, change the MySQL password for the `wordpress` user using the `mysql`
|
||||||
|
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 <CONTAINER_ID> \
|
||||||
|
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`).
|
|
@ -66,7 +66,7 @@ $ docker service create --name helloworld alpine ping docker.com
|
||||||
9uk4639qpg7npwf3fn2aasksr
|
9uk4639qpg7npwf3fn2aasksr
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuring services
|
## Configure services
|
||||||
|
|
||||||
When you create a service, you can specify many different configuration options
|
When you create a service, you can specify many different configuration options
|
||||||
and constraints. See the output of `docker service create --help` for a full
|
and constraints. See the output of `docker service create --help` for a full
|
||||||
|
@ -99,6 +99,12 @@ $ docker service create --name helloworld \
|
||||||
9uk4639qpg7npwf3fn2aasksr
|
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
|
### Specify the image version the service should use
|
||||||
|
|
||||||
When you create a service without specifying any details about the version of
|
When you create a service without specifying any details about the version of
|
||||||
|
|
Loading…
Reference in New Issue