Move docs from GitHub Discussions to the repo

master
Nicolas Favre-Felix 3 years ago
parent 908c383838
commit b8a43f03dc
No known key found for this signature in database
GPG Key ID: C04E7AA8B6F73372

@ -71,7 +71,7 @@ Administrative keys for nicolas/webdis:0.1.19
Root Key: 40be21f47831d593892370a8e3fc5bfffb16887c707bd81a6aed2088dc8f4bef Root Key: 40be21f47831d593892370a8e3fc5bfffb16887c707bd81a6aed2088dc8f4bef
``` ```
The signing keys are listed on [this documentation page](https://github.com/nicolasff/webdis/discussions/211#:~:text=%F0%9F%94%91-,Key%20IDs,-The%20SIGNER%20field); please make sure they match what you see. The signing keys are listed on [this documentation page](docs/webdis-docker-content-trust.md#-key-ids); please make sure they match what you see.
### Amazon Elastic Container Registry (ECR) ### Amazon Elastic Container Registry (ECR)
@ -87,7 +87,7 @@ The consequence is that [Webdis images on ECR](https://gallery.ecr.aws/nicolas/w
They can still be verified, since the images uploaded there use the exact same hash as the ones on Docker Hub, which _are_ signed. This means that you can verify the signature using the `docker trust inspect` command described above, as long as you **also** make sure that the image hash associated with the image on ECR matches the one shown on Docker Hub. They can still be verified, since the images uploaded there use the exact same hash as the ones on Docker Hub, which _are_ signed. This means that you can verify the signature using the `docker trust inspect` command described above, as long as you **also** make sure that the image hash associated with the image on ECR matches the one shown on Docker Hub.
For more details about Content Trust validation with ECR images, refer to the article titled [Webdis and Docker Content Trust](https://github.com/nicolasff/webdis/discussions/211) in the Discussion section. For more details about Content Trust validation with ECR images, refer to the article titled [Webdis and Docker Content Trust](docs/webdis-docker-content-trust.md#webdis-and-docker-content-trust) in the [Webdis documentation](docs/README.md#webdis-documentation).
## Multi-architecture images ## Multi-architecture images
@ -176,7 +176,7 @@ See also the [Hiredis docs](https://github.com/redis/hiredis/blob/v1.0.2/README.
### Running Redis and Webdis with SSL in Docker Compose ### Running Redis and Webdis with SSL in Docker Compose
For a full tutorial showing how to configure and run Redis and Webdis under Docker Compose with SSL connections between the two services, head to the Discussions tab at the top of this page and open [Running Webdis & Redis in Docker Compose with SSL connections](https://github.com/nicolasff/webdis/discussions/203). For a full tutorial showing how to configure and run Redis and Webdis under Docker Compose with SSL connections between the two services, head to the `docs` folder and open [Running Webdis & Redis in Docker Compose with SSL connections](docs/webdis-redis-docker-compose-ssl.md).
## SSL troubleshooting ## SSL troubleshooting

@ -0,0 +1,9 @@
# Webdis Documentation
[**Webdis and Docker Content Trust:**](webdis-docker-content-trust.md#webdis-and-docker-content-trust) How to validate the authenticity of Webdis Docker images with cryptographic signatures using Docker Content Trust.
[**Running Webdis & Redis in Docker Compose:**](webdis-redis-docker-compose.md#running-webdis--redis-in-docker-compose) How to run Webdis in one container, Redis in another, with the two configured as part of a single Docker Compose stack.
[**Running Webdis & Redis in Docker Compose with SSL connections:**](webdis-redis-docker-compose-ssl.md#running-webdis--redis-in-docker-compose-with-ssl-connections) How to run Webdis in one container, Redis in another, the two communicating securely with encrypted connections using TLS as part of a single Docker Compose stack.
**(more coming in the future!)**

@ -0,0 +1,403 @@
# Webdis and Docker Content Trust
Docker images for Webdis are signed using [Docker Content Trust (DCT)](https://docs.docker.com/engine/security/trust/). This means that you can verify that a Docker image you pulled for Webdis is legitimate and was built by the author of Webdis, rather than by an unknown third-party.
Docker images for Webdis are published on Docker Hub and Amazon Elastic Container Registry (ECR):
- On Docker Hub, they are under `nicolas/webdis`: https://hub.docker.com/r/nicolas/webdis
- On ECR, they are also under `nicolas/webdis`: https://gallery.ecr.aws/nicolas/webdis
**Important:** Only images starting with [release 0.1.12](https://github.com/nicolasff/webdis/releases/tag/0.1.12) are signed. Images starting with [release 0.1.19](https://github.com/nicolasff/webdis/releases/tag/0.1.19) are published as multi-architecture manifests, so validating them involves different steps.
## Docker Hub vs AWS Elastic Container Registry
An important difference between the two services is that Docker Hub supports notarized (signed) images, but ECR **does not** (it is apparently [in the works](https://d2908q01vomqb2.cloudfront.net/fe2ef495a1152561572949784c16bf23abb28057/2020/08/21/C3-ECR-Security-Best-Practices_072020_v3-no-notes.pdf#page=7)).
If you use Docker Hub, just use `docker trust inspect` and compare the signing keys to those in this article, regardless of the version of Webdis. The keys are listed in [the next section of this document](#-key-ids).
Things are significantly more complex if you want to validate images pulled from ECR. The main idea is to compare the hash of an image pulled from ECR with the hash of a (signed) image pulled from Docker Hub. If they match and if the Docker Hub image can be validated with `docker trust inspect`, you can be certain that you've downloaded the exact same image from ECR as the one from Docker Hub.
🛑 If the images don't match, or if the signatures in `docker trust inspect` do not use the same keys as the ones documented here, something is wrong and you should **not** run the unknown image.
## Validation with `docker trust inspect` (Docker Hub only)
🐳 This process applies **only** to images downloaded from Docker Hub.
To validate an image, use `docker trust inspect` followed by the image name and version, and compare the keys fingerprints listed in the output with the ones documented here.
First, pull the image:
```
$ docker pull nicolas/webdis:0.1.19
0.1.19: Pulling from nicolas/webdis
Digest: sha256:5de58646bae3ee52e05a65672532120b094682b79823291031ccb41533c21667
Status: Image is up to date for nicolas/webdis:0.1.19
docker.io/nicolas/webdis:0.1.19
```
Then, inspect its content trust metadata:
```
$ docker trust inspect --pretty nicolas/webdis:0.1.19
Signatures for nicolas/webdis:0.1.19
SIGNED TAG DIGEST SIGNERS
0.1.19 5de58646bae3ee52e05a65672532120b094682b79823291031ccb41533c21667 (Repo Admin)
List of signers and their keys for nicolas/webdis:0.1.19
SIGNER KEYS
nicolasff dd0768b9d35d
Administrative keys for nicolas/webdis:0.1.19
Repository Key: fed0b56b8a8fd4d156fb2f47c2e8bd3eb61948b72a787c18e2fa3ea3233bba1a
Root Key: 40be21f47831d593892370a8e3fc5bfffb16887c707bd81a6aed2088dc8f4bef
```
## 🔑 Key IDs
- The `SIGNER` field tells you who signed the image; it should be `nicolasff`. The short key ID is `dd0768b9d35d`, the full ID being `dd0768b9d35d344bbd1681418d27052c4c896a59be214352448daa2b6925b95b`.
- The Repository Key is scoped to the Docker Hub repo, `nicolas/webdis`. This should match as well. Its key ID is `fed0b56b8a8fd4d156fb2f47c2e8bd3eb61948b72a787c18e2fa3ea3233bba1a`.
- Finally, the Root Key ID is `40be21f47831d593892370a8e3fc5bfffb16887c707bd81a6aed2088dc8f4bef`.
Make sure that **all** the key IDs mentioned in the output of `docker trust inspect` are listed here.
✅ If you downloaded the image from Docker Hub, you can stop here.
## Validation of images downloaded from AWS ECR
Since ECR does not support Content Trust, the only way to validate the integrity of images downloaded from ECR is to download the same image from Docker Hub, validate its signature, and compare the image digests between the one from Docker Hub and the one from ECR.
It is tedious, but this seems to be the only workaround until AWS implements this feature.
## AWS ECR only: validation of single-architecture images (versions 0.1.12 to 0.1.18)
Given an image version, download it from both Docker Hub and AWS ECR. Let's do this for `0.1.18`.
First, from Docker Hub:
```
$ docker pull nicolas/webdis:0.1.18
0.1.18: Pulling from nicolas/webdis
Digest: sha256:6def97f1299c4de2046b1ae77427a7fa41552c91d3ae02059f79dbcb0650fe9e
Status: Image is up to date for nicolas/webdis:0.1.18
docker.io/nicolas/webdis:0.1.18
```
Then, from AWS ECR:
```
$ docker pull public.ecr.aws/nicolas/webdis:0.1.18
0.1.18: Pulling from nicolas/webdis
Digest: sha256:6def97f1299c4de2046b1ae77427a7fa41552c91d3ae02059f79dbcb0650fe9e
Status: Downloaded newer image for public.ecr.aws/nicolas/webdis:0.1.18
public.ecr.aws/nicolas/webdis:0.1.18
```
We can already see that the two lines starting with `Digest:` show the same hash.
To compare the images themselves, we can use `docker image inspect` and compare the `Id` fields:
```
$ docker image inspect nicolas/webdis:0.1.18 | grep -w Id
"Id": "sha256:ecadadde26d4b78216b1b19e903a116ebcd824ae7f27963c5e3518ab1a58d859",
$ docker image inspect public.ecr.aws/nicolas/webdis:0.1.18 | grep -w Id
"Id": "sha256:ecadadde26d4b78216b1b19e903a116ebcd824ae7f27963c5e3518ab1a58d859",
```
Both of them also have the same `RepoTags` in the full `docker image inspect` output.
Now that we know that the image we pulled from ECR is the exact same as the one from Docker Hub, we can follow the trust validation steps for Docker Hub [documented above](#validation-with-docker-trust-inspect-docker-hub-only) and know that since we could validate the signature of a Docker Hub image that is **identical** to our ECR image, the ECR image is also legit.
✅ If you wanted to validate an ECR image between `0.1.12` and `0.1.18`, you can stop here.
## AWS ECR only: validation of multi-architecture images (versions 0.1.19 and above)
Multi-architecture images are built using a _manifest list_, which is a small file that references multiple manifests. In turn, a Docker image manifest contains information about a single image, such as its size, layers, digest, etc:
```
docker.io/nicolas/webdis:0.1.19
┌─Docker Hub Manifest List─┐
│ ┌─────────────────────┐ │
│ │ type: manifest.v2 │ │
│ │ digest: sha256:AAAA─┼──┼────► docker.io/nicolas/webdis@sha256:AAAA
│ │ platform: │ │ │
│ │ arch: amd64 │ │ ▼
│ │ os: linux │ │ ┌─Docker Hub Manifest─┐
│ └─────────────────────┘ │ │ type: image.v1 │ ┌────Docker Image─────┐
│ ┌─────────────────────┐ │ │ digest: sha256:FFFF─┼──────►│ Id: sha256:FFFF │
│ │ type: manifest.v2 │ │ │ size: 2737 │ │ Architecture: amd64 │
│ │ digest: sha256:BBBB │ │ │ layers: 5 [...] │ │ Os: linux │
│ │ platform: │ │ └─────────────────────┘ │ Size: 11096513 │
│ │ arch: arm64 │ │ │ Config: │
│ │ os: linux │ │ │ ExposedPorts: │
│ │ variant: v8 │ │ │ - 7379/TCP │
│ └─────────────────────┘ │ │ Layers: [...] │
└──────────────────────────┘ └─────────────────────┘
```
With these images, it's the _manifest hash_ that is signed. By validating a signature on the manifest hash, you can guarantee that the contents of the manifest have not been altered which means you can trust the images that it points to.
## Structure of a multi-architecture Webdis release
Multi-architecture releases of Webdis use two manifest lists: one for Docker Hub and one for AWS Elastic Container Registry (ECR).
Each manifest list is tagged with the release itself (e.g. `0.1.19`), and points to two _manifests_ each describing an image of a single architecture at this version.
The entry point for a `docker pull` is just the repo and the version; here the repo could be on Docker Hub or AWS ECR.
If we run `docker manifest inspect` on a multi-architecture manifest, we get this manifest list with two entries:
```
$ docker manifest inspect docker.io/nicolas/webdis:0.1.19
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1365,
"digest": "sha256:2ced2d99146e1bcaf10541d17dbac573cffd02237c3b268875be1868138d3b54",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1365,
"digest": "sha256:d026c5675552947b6a755439dfd58360e44a8860436f4eddfe9b26d050801248",
"platform": {
"architecture": "arm64",
"os": "linux",
"variant": "v8"
}
}
]
}
```
Each manifest is identified by its hash, and the `platform` metadata shows us the difference between the two manifests in this manifest list:
- For x86-64, the manifest hash is `sha256:2ced2d99146e1bcaf10541d17dbac573cffd02237c3b268875be1868138d3b54`.
- For ARM64v8, the manifest hash is `sha256:d026c5675552947b6a755439dfd58360e44a8860436f4eddfe9b26d050801248`.
Note that we didn't run `docker image inspect` since we're not dealing with images yet, only a manifest list so far.
If we look at the manifest for `amd64` (same as x86-64), we see that it references a single Docker image:
```
$ docker manifest inspect docker.io/nicolas/webdis@sha256:2ced2d99146e1bcaf10541d17dbac573cffd02237c3b268875be1868138d3b54
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"schemaVersion": 2,
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"digest": "sha256:010021e00e0910d0b73d0bfc9cb58ce583e96f3ad69fc6ee4a7a41baa707d7f7",
"size": 2728
},
"layers": [ "... layers omitted here for brevity ..." ]
}
```
It's important to note that this manifest does not have a particular tag or human-assigned label/name, so we refer to it by its hash, and here again we ran `docker manifest inspect` since this is still not an image.
But this time, the manifest finally points to an image (see `mediaType`), with SHA-256 digest `010021e0…`. If instead of running `docker manifest inspect` we run `docker inspect` (with the same hash as in the previous command), this time we get the same familiar as the one for an image:
```
$ docker inspect docker.io/nicolas/webdis@sha256:2ced2d99146e1bcaf10541d17dbac573cffd02237c3b268875be1868138d3b54
[
{
"Id": "sha256:010021e00e0910d0b73d0bfc9cb58ce583e96f3ad69fc6ee4a7a41baa707d7f7",
"RepoTags": [
"nicolas/webdis:0.1.19",
"nicolas/webdis:0.1.19-amd64",
"nicolas/webdis:latest",
"public.ecr.aws/nicolas/webdis:0.1.19-amd64"
],
"...",
"Config": {
"Labels": {
"org.opencontainers.image.base.name": "docker.io/library/alpine:3.14.3",
"org.opencontainers.image.created": "2021-12-23T22:46:35-0800",
"org.opencontainers.image.description": "Webdis 0.1.19",
"org.opencontainers.image.licenses": "BSD-2-Clause",
"org.opencontainers.image.revision": "417e0ac48345d849cd37db0a473d763b47195c23",
"org.opencontainers.image.source": "https://github.com/nicolasff/webdis/tree/0.1.19",
"org.opencontainers.image.title": "Webdis 0.1.19",
"org.opencontainers.image.url": "https://hub.docker.com/r/nicolas/webdis",
"org.opencontainers.image.version": "0.1.19"
}
},
"Architecture": "amd64",
"Os": "linux",
"Size": 12078773,
"..."
}
]
```
## Validating trust for multi-architecture releases
Now that the structure is hopefully clear, let's look at how we can validate the integrity of our images.
The main challenge here is with AWS ECR, so let's start with Docker Hub manifest lists.
With Docker Hub, we can still use `docker trust inspect repo/image:version` to validate its signatures.
Here's its output, cleaned up with `grep` and `sed` for brevity but without losing any information:
```
$ docker trust inspect docker.io/nicolas/webdis:0.1.19 | grep -Ew '[A-Z][A-Za-z]+' | sed -E -e 's/ {8}/ /g' | sed -E -e 's/"|(^ {2})|((\[|,)$)//g'
Name: docker.io/nicolas/webdis:0.1.19
SignedTags:
SignedTag: 0.1.19
Digest: 5de58646bae3ee52e05a65672532120b094682b79823291031ccb41533c21667
Signers:
Repo Admin
Signers:
Name: nicolasff
Keys:
ID: dd0768b9d35d344bbd1681418d27052c4c896a59be214352448daa2b6925b95b
AdministrativeKeys:
Name: Root
Keys:
ID: 40be21f47831d593892370a8e3fc5bfffb16887c707bd81a6aed2088dc8f4bef
Name: Repository
Keys:
ID: fed0b56b8a8fd4d156fb2f47c2e8bd3eb61948b72a787c18e2fa3ea3233bba1a
```
To validate the keys, refer to the list above in the "Key IDs" section.
### Validating the signed object
The `SignedTag` at the beginning of the output mentions `0.1.19`, but also a digest. This is the digest of the Manifest List that the name `0.1.19` points to. You can compute this digest yourself to make sure it matches what `docker trust inspect` returned.
An important point here is that Docker computes digests **without** a terminating new line for the JSON being hashed, but in a terminal it always adds it for readability. To compute the digest, you need to remove this new line; you can do this with `| perl -pe 'chomp if eof'`. All together:
```
$ docker manifest inspect docker.io/nicolas/webdis:0.1.19 | perl -pe 'chomp if eof' | shasum -a 256
5de58646bae3ee52e05a65672532120b094682b79823291031ccb41533c21667 -
```
(change `shasum -a 256` to `sha256sum` on GNU/Linux)
✅ Note that this matches the hash we found in `SignedTag`.
With this, we **know** that the manifest list was not altered. From the manifest list, we can find the two manifests for the two architectures, and from those we can verify that the image digests referenced on Docker Hub are the same as the image digests referenced on AWS ECR.
The chain of trust goes like this:
- Docker Hub manifest list (signed)
- Docker Hub manifest for x86-64 (referenced in list whose matching hash we checked)
- Image for x86-64 (transitively validated)
- AWS ECR manifest list (not signed)
- AWS ECR manifest for x86-64 (not signed)
- Image for x86-64 (same hash as the one referenced in Docker Hub ⇒ therefore can be trusted)
The same applies for ARM64v8 images, of course.
### Putting it all together
Validate the signatures with Docker Hub:
```
$ docker trust inspect --pretty docker.io/nicolas/webdis:0.1.19
[ ... make sure the keys are all valid ... ]
```
Extract the manifest hashes from the Docker Hub manifest list:
```
$ docker manifest inspect nicolas/webdis:0.1.19 | grep -E 'digest|architecture'
"digest": "sha256:2ced2d99146e1bcaf10541d17dbac573cffd02237c3b268875be1868138d3b54",
"architecture": "amd64",
"digest": "sha256:d026c5675552947b6a755439dfd58360e44a8860436f4eddfe9b26d050801248",
"architecture": "arm64",
```
Examine the Docker Hub manifest for the architecture you're running:
```
$ docker manifest inspect nicolas/webdis@sha256:2ced2d99146e1bcaf10541d17dbac573cffd02237c3b268875be1868138d3b54 | grep -A3 config
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"digest": "sha256:010021e00e0910d0b73d0bfc9cb58ce583e96f3ad69fc6ee4a7a41baa707d7f7",
"size": 2728
```
Note the digest: `sha256:010021e00e0910d0b73d0bfc9cb58ce583e96f3ad69fc6ee4a7a41baa707d7f7`.
Repeat the last two steps for ECR, first extracting the manifest hashes from the ECR manifest list:
```
$ docker manifest inspect public.ecr.aws/nicolas/webdis:0.1.19 | grep -E 'digest|architecture'
"digest": "sha256:ec6a77ec083a659d3293810542c07bc1eee74e148cb02448cca3bfb260d7c19c",
"architecture": "amd64",
"digest": "sha256:d78f48b96464cd31bb1c29b01bcdaceac28c2ccb2d52a294cdf4cf840f5b6433",
"architecture": "arm64",
```
These are different, but do they point to the same images?
```
$ docker manifest inspect public.ecr.aws/nicolas/webdis@sha256:ec6a77ec083a659d3293810542c07bc1eee74e148cb02448cca3bfb260d7c19c | grep -A3 config
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 2728,
"digest": "sha256:010021e00e0910d0b73d0bfc9cb58ce583e96f3ad69fc6ee4a7a41baa707d7f7"
```
✅ Yes, this is the same image as the trusted image fom Docker Hub.
## Relationship diagram between Manifest Lists, Manifests, and Images
In case this helps make sense of the way all these objects are connected and reference each other, here's a diagram. Note how the two Manifest Lists point to different Manifests, but that these Manifests point to the same Images.
The Manifest List "entry point" is underlined in bold.
```
┌────Docker Image─────┐
│ Id: sha256:FFFF │
│ Architecture: amd64 │ (both manifests reference the same image)
│ Os: linux │
│ Size: 11096513 │
└─────────────────────┘
▲ ▲
│ └───────────────────────────────┐
┌─────►┌─Docker Hub Manifest─┐ │ ┌─►┌─Docker Hub Manifest─┐ │
│ │ type: image.v1 │ │ │ │ type: image.v1 │ │
│ │ digest: sha256:FFFF─┼────┘ │ │ digest: sha256:FFFF─┼───┘
│ │ layers: 5 [...] │ │ │ layers: 5 [...] │
│ └─────────────────────┘ │ └─────────────────────┘
│ │
└─docker.io/nicolas/webdis@sha256:AAAA └──public.ecr.aws/nicolas/webdis@sha256:CCCC
▲ ▲
DOCKER HUB ENTRY POINT: │ AWS ECR ENTRY POINT: │
┌──docker.io/nicolas/webdis:0.1.19 │ ┌──public.ecr.aws/nicolas/webdis:0.1.19 │
│ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │ │ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │
│ │ │ │
└───────────►┌─Docker Hub Manifest List─┐ │ └──►┌──AWS ECR Manifest List───┐ │
│ (v0.1.19, 2 manifests) │ │ │ (v0.1.19, 2 manifests) │ │
│ ┌─────────────────────┐ │ │ │ ┌─────────────────────┐ │ │
│ │ type: manifest.v2 │ │ │ │ │ type: manifest.v2 │ │ │
│ │ digest: sha256:AAAA─┼──┼─┘ │ │ digest: sha256:CCCC─┼──┼─────────┘
│ │ arch: amd64 │ │ │ │ arch: amd64 │ │
│ │ os: linux │ │ │ │ os: linux │ │
│ └─────────────────────┘ │ │ └─────────────────────┘ │
(signed) │ ┌─────────────────────┐ │ │ ┌─────────────────────┐ │ (not signed)
│ │ type: manifest.v2 │ │ │ │ type: manifest.v2 │ │
│ │ digest: sha256:BBBB─┼──┼─┐ │ │ digest: sha256:DDDD─┼──┼─────────┐
│ │ arch: arm64 │ │ │ │ │ arch: arm64 │ │ │
│ │ os: linux │ │ │ │ │ os: linux │ │ │
│ │ variant: v8 │ │ │ │ │ variant: v8 │ │ │
│ └─────────────────────┘ │ │ │ └─────────────────────┘ │ │
└──────────────────────────┘ │ └──────────────────────────┘ │
▼ ▼
┌──docker.io/nicolas/webdis@sha256:BBBB ┌─public.ecr.aws/nicolas/webdis@sha256:DDDD
│ │
└──────►┌─Docker Hub Manifest─┐ └─►┌──AWS ECR Manifest───┐
│ type: image.v1 │ │ type: image.v1 │
│ digest: sha256:EEEE─┼─┐ │ digest: sha256:EEEE─┼─┐
│ layers: 5 [...] │ │ │ layers: 5 [...] │ │
└─────────────────────┘ │ └─────────────────────┘ │
│ ┌──────────────────────────────────┘
▼ ▼
┌────Docker Image─────┐
│ Id: sha256:EEEE │
│ Architecture: arm64 │ (both manifests reference the same image)
│ Os: linux │
│ Size: 12201637 │
└─────────────────────┘
```

@ -0,0 +1,274 @@
# Running Webdis & Redis in Docker Compose with SSL connections
This page describes how to start Redis and Webdis in [Docker Compose](https://docs.docker.com/compose/), with secure connections between the two.
## Requirements
For this, we'll need:
1. Docker Compose (you should have it if you use [Docker Desktop](https://www.docker.com/products/docker-desktop))
2. Redis version 6 or newer (we'll use a Docker image)
3. Webdis version 0.1.18 or newer (also in a Docker image)
4. A client certificate and key
5. A CA certificate
6. The `openssl` command-line tool
7. Optionally, `curl` for downloading a few files
We'll keep all our files together in a `playground` directory:
```shell
mkdir playground
cd playground
```
## SSL configuration
Let's start by generating the files required to encrypt connections. These instructions are adapted from the `Makefile` on [this page](https://nishanths.svbtle.com/setting-up-redis-with-tls).
### CA certificate
First, the CA cert. Generate a key for it, and then the cert:
```shell
openssl genrsa 4096 > ./ca.key
openssl req -new -x509 -nodes -sha256 -key ./ca.key -days 3650 \
-subj "/C=AU/CN=example" -out ./ca.crt
```
### Certificate Signing Request (CSR)
Let's start with a custom OpenSSL config file (change the path from `/etc/ssl` if your `openssl.cnf` is located elsewhere, e.g. at `/etc/pki/tls/openssl.cnf` on Fedora).
```shell
cp /etc/ssl/openssl.cnf .
echo >> ./openssl.cnf
echo '[ san_env ]' >> ./openssl.cnf
echo 'subjectAltName = IP:127.0.0.1' >> ./openssl.cnf
```
<details>
<summary>If you can't find your openssl.cnf, click here to show a basic file you can use.</summary>
Save the following block as `openssl.cnf` in your `playground` directory:
```ini
[ req ]
distinguished_name = req_distinguished_name
attributes = req_attributes
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_min = 2
countryName_max = 2
stateOrProvinceName = State or Province Name (full name)
localityName = Locality Name (eg, city)
0.organizationName = Organization Name (eg, company)
organizationalUnitName = Organizational Unit Name (eg, section)
commonName = Common Name (eg, fully qualified host name)
commonName_max = 64
emailAddress = Email Address
emailAddress_max = 64
[ req_attributes ]
challengePassword = A challenge password
challengePassword_min = 4
challengePassword_max = 20
[ san_env ]
subjectAltName = IP:127.0.0.1
```
</details>
Then we can create the CSR and key:
```shell
export SAN='IP:127.0.0.1'
openssl req -reqexts san_env -extensions san_env -config ./openssl.cnf \
-newkey rsa:4096 -nodes -sha256 -keyout ./redis.key \
-subj "/C=AU/CN=127.0.0.1" -out ./redis.csr
```
Make sure this command created `redis.key` and `redis.csr`.
### Certificate
Finally, let's generate the certificate:
```shell
openssl x509 -req -sha256 -extfile ./openssl.cnf -extensions san_env \
-days 3650 -in ./redis.csr -CA ./ca.crt -CAkey ./ca.key \
-CAcreateserial -out ./redis.crt
```
We should now have `ca.crt`, `redis.key`, and `redis.crt`. We'll need these 3 files to configure the encrypted connections between Webdis and Redis. The other files generated by `openssl` (`redis.csr` and `ca.key`) are not needed by Redis or Webdis.
## Docker Compose directory structure
Let's start with the config files needed by Redis and Webdis; we'll keep them all in a local directory that is mounted by the containers.
```shell
mkdir config
cp ca.crt redis.key redis.crt ./config
curl -sL -o ./config/webdis.json https://github.com/nicolasff/webdis/raw/0.1.18/webdis.json
curl -sL -o ./config/redis.conf https://github.com/redis/redis/raw/6.2.6/redis.conf
```
If you don't have `curl`, use the two URLs above to fetch `webdis.json` and `redis.conf` and move them to the `config` directory under `playground`.
## Service configuration
### Webdis
Edit `./config/webdis.json` and set:
- `"redis_host"` to `"redis"`
- `"redis_port"` to `6380`
- `"logfile"` to `"/dev/stderr"`
And add a key named `"ssl"` at the same depth as the two keys above (e.g. just under `"database": 0`), pointing to:
```json
{
"enabled": true,
"ca_cert_bundle": "/config/ca.crt",
"client_cert": "/config/redis.crt",
"client_key": "/config/redis.key"
},
```
<details>
<summary>Click here to see what webdis.json should look like after these changes.</summary>
```json
{
"redis_host": "redis",
"redis_port": 6380,
"http_host": "0.0.0.0",
"http_port": 7379,
"threads": 5,
"pool_size": 20,
"daemonize": false,
"websockets": false,
"database": 0,
"ssl": {
"enabled": true,
"ca_cert_bundle": "/config/ca.crt",
"client_cert": "/config/redis.crt",
"client_key": "/config/redis.key"
},
"acl": [
{
"disabled": ["DEBUG"]
},
{
"http_basic_auth": "user:password",
"enabled": ["DEBUG"]
}
],
"verbosity": 4,
"logfile": "/dev/stderr"
}
```
</details>
If you have `jq` installed, you can validate that it is valid JSON with:
```shell
jq empty ./config/webdis.json && echo 'VALID' || echo 'INVALID'
```
### Redis
Then, edit `./config/redis.conf` and uncomment the following lines and set their values as listed:
- `tls-port 6380` (on line 145, this should initially say `# tls-port 6379`, make sure to change the port number)
- `tls-cert-file /config/redis.crt` (on line 151)
- `tls-key-file /config/redis.key` (on line 152)
- `tls-ca-cert-file /config/ca.crt` (on line 184)
Then change line 75 which starts with `bind`, so that it looks like this:
```
bind 0.0.0.0
```
You can also grab `redis.conf` from [this Gist](https://gist.github.com/nicolasff/513d3ebd9d6f4268d6deb1d979fa44b8) which contains a Redis 6.2.6 config file with the required changes.
## Docker Compose configuration
Create a new file named `docker-compose.yml` in your `playground` directory, with the following contents:
```yaml
services:
webdis:
image: nicolas/webdis:0.1.18
command: /usr/local/bin/webdis-ssl /config/webdis.json
volumes: # mount volume containing the config files
- ./config:/config
networks:
- secure
depends_on: # make sure Redis starts first, so that Webdis can connect to it without retries
- redis
ports: # allow connections from the Docker host on localhost, port 7379
- "127.0.0.1:7379:7379"
redis:
image: redis:6.2.6
command: /usr/local/bin/redis-server /config/redis.conf
volumes: # mount volume containing the config files
- ./config:/config
networks:
- secure
expose: # make the TLS port from Redis visible to Webdis
- "6380"
networks:
secure:
```
This configures two services named `webdis` and `redis`, sharing a common network named `secure`. With the `expose` property Redis allows connections from Webdis on port 6380. Both containers mount the local `config` directory under `/config` and start their binaries using the configuration files we've just created and edited. Finally, Webdis also allows binds its (container) port 7379 to the hosts's loopback interface also on port 7379. This will let us run `curl` locally to connect to Webdis from the host.
**Note:** While the Webdis Docker image does bundle a Redis binary, it makes more sense to use multiple containers to demonstrate the use of SSL connections. This bundled Redis service does not run in this example, since we replace the Webdis command with one that only starts Webdis instead of starting Redis and Webdis together in the same container.
## Start the Docker Compose stack
From the `playground` directory, run:
```shell
docker-compose up
```
You should see both services logging to the console in different colors, with an output like:
```none
Creating playground_redis_1 ... done
Creating playground_webdis_1 ... done
Attaching to playground_redis_1, playground_webdis_1
redis_1 | 1:C 23 Oct 2021 01:42:49.704 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
redis_1 | 1:C 23 Oct 2021 01:42:49.705 # Redis version=6.2.6, bits=64, commit=00000000, modified=0, pid=1, just started
redis_1 | 1:C 23 Oct 2021 01:42:49.705 # Configuration loaded
redis_1 | 1:M 23 Oct 2021 01:42:49.716 * monotonic clock: POSIX clock_gettime
webdis_1 | [1] 23 Oct 01:42:51 I Webdis listening on port 7379
webdis_1 | [1] 23 Oct 01:42:51 I Webdis 0.1.18 up and running
redis_1 | 1:M 23 Oct 2021 01:42:49.717 * Running mode=standalone, port=6379.
redis_1 | 1:M 23 Oct 2021 01:42:49.717 # Server initialized
redis_1 | 1:M 23 Oct 2021 01:42:49.718 * Ready to accept connections
```
You can now run commands against Webdis by connecting to port 7379 on `localhost`, e.g.
```sh
$ curl -s 'http://localhost:7379/ping'
{"ping":[true,"PONG"]}
$ curl -s 'http://localhost:7379/info' | jq -r .info.uptime_in_seconds
27
```
## Clean-up
Stop the services with ctrl-c and remove the entire Docker Compose stack by running `docker-compose rm` from the `playground` directory.

@ -0,0 +1,100 @@
# Running Webdis & Redis in Docker Compose
This page describes how to start Redis and Webdis in [Docker Compose](https://docs.docker.com/compose/). A different page describes a variant of this model, where connections from Webdis to Redis are encrypted: see "[Running Webdis & Redis in Docker Compose with SSL connections](webdis-redis-docker-compose-ssl.md#running-webdis--redis-in-docker-compose-with-ssl-connections)".
## Setup
We'll keep all our files together in a `playground` directory:
```shell
mkdir playground
cd playground
```
The files we'll need are:
1. A config file for webdis, named `webdis.json`
2. A Compose file for Docker Compose, named `docker-compose.yml`
### Webdis configuration
First, download `webdis.json` from GitHub by following [this link](https://github.com/nicolasff/webdis/raw/0.1.19/webdis.json) or running this `curl` command:
```sh
curl -sL -o ./webdis.json https://github.com/nicolasff/webdis/raw/0.1.19/webdis.json
```
Edit `./webdis.json` in the `playground` directory and set:
- `"redis_host"` to `"redis"`
- `"logfile"` to `"/dev/stderr"`
### Docker Compose configuration
Create a new file named `docker-compose.yml` in your `playground` directory, with the following contents:
```yaml
services:
webdis:
image: nicolas/webdis:latest
command: /usr/local/bin/webdis /config/webdis.json
volumes: # mount volume containing the config file
- ./:/config
networks:
- shared
depends_on: # make sure Redis starts first, so that Webdis can connect to it without retries
- redis
ports: # allow connections from the Docker host on localhost, port 7379
- "127.0.0.1:7379:7379"
redis:
image: redis:6.2.6
networks:
- shared
ports: # make the Redis port visible to Webdis
- "6379:6379"
networks:
shared:
```
This configures two services named `webdis` and `redis`, sharing a common network named `shared`. With the `expose` property, Redis allows connections from Webdis on port 6379. The `webdis` container mounts the local `playground` directory under `/config` and starts its binary using the configuration file we've just downloaded and edited. Finally, Webdis also allows binds its (container) port 7379 to the hosts's loopback interface also on port 7379. This will let us run `curl` locally to connect to Webdis from the host.
**Note:** While the Webdis Docker image does bundle a Redis binary, it makes more sense to use multiple containers to demonstrate the use of SSL connections. This bundled Redis service does not run in this example, since we replace the Webdis command with one that only starts Webdis instead of starting Redis and Webdis together in the same container.
## Start the Docker Compose stack
From the `playground` directory, run:
```shell
docker-compose up
```
You should see both services logging to the console in different colors, with an output like:
```none
Creating playground_redis_1 ... done
Creating playground_webdis_1 ... done
Attaching to playground_redis_1, playground_webdis_1
redis_1 | 1:C 30 Oct 2021 06:14:55.150 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
redis_1 | 1:C 30 Oct 2021 06:14:55.150 # Redis version=6.2.6, bits=64, commit=00000000, modified=0, pid=1, just started
redis_1 | 1:C 30 Oct 2021 06:14:55.150 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
redis_1 | 1:M 30 Oct 2021 06:14:55.152 * monotonic clock: POSIX clock_gettime
redis_1 | 1:M 30 Oct 2021 06:14:55.153 * Running mode=standalone, port=6379.
redis_1 | 1:M 30 Oct 2021 06:14:55.154 # Server initialized
redis_1 | 1:M 30 Oct 2021 06:14:55.155 * Ready to accept connections
webdis_1 | [1] 30 Oct 06:14:56 I Webdis listening on port 7379
webdis_1 | [1] 30 Oct 06:14:56 I Webdis 0.1.19 up and running
```
You can now run commands against Webdis by connecting to port 7379 on `localhost`, e.g.
```sh
$ curl -s 'http://localhost:7379/ping'
{"ping":[true,"PONG"]}
$ curl -s 'http://localhost:7379/info' | jq -r .info.uptime_in_seconds
27
```
## Clean-up
Stop the services with ctrl-c and remove the entire Docker Compose stack by running `docker-compose rm` from the `playground` directory.
Loading…
Cancel
Save