From 0ddf483ee353121d11a52928af8c3681c6e7fe69 Mon Sep 17 00:00:00 2001 From: Elton Stoneman Date: Mon, 28 Nov 2016 15:27:42 +0000 Subject: [PATCH 1/3] Added registry Windows lab Signed-off-by: Elton Stoneman --- windows/registry/Dockerfile | 11 ++ windows/registry/Dockerfile.builder | 13 +++ windows/registry/README.md | 33 ++++++ windows/registry/part-1.md | 102 ++++++++++++++++++ windows/registry/part-2.md | 153 +++++++++++++++++++++++++++ windows/registry/part-3.md | 154 ++++++++++++++++++++++++++++ windows/registry/part-4.md | 114 ++++++++++++++++++++ 7 files changed, 580 insertions(+) create mode 100644 windows/registry/Dockerfile create mode 100644 windows/registry/Dockerfile.builder create mode 100644 windows/registry/README.md create mode 100644 windows/registry/part-1.md create mode 100644 windows/registry/part-2.md create mode 100644 windows/registry/part-3.md create mode 100644 windows/registry/part-4.md diff --git a/windows/registry/Dockerfile b/windows/registry/Dockerfile new file mode 100644 index 0000000..c5f4068 --- /dev/null +++ b/windows/registry/Dockerfile @@ -0,0 +1,11 @@ +# escape=` +FROM microsoft/nanoserver +SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';"] + +EXPOSE 5000 +ENV REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=c:\\data + +WORKDIR c:\\registry +COPY ./registry/ . + +CMD ["registry", "serve", "config.yml"] \ No newline at end of file diff --git a/windows/registry/Dockerfile.builder b/windows/registry/Dockerfile.builder new file mode 100644 index 0000000..b15132c --- /dev/null +++ b/windows/registry/Dockerfile.builder @@ -0,0 +1,13 @@ +# escape=` + +# REMARK - when Go 1.8 is released we can use an official image for the base. +# There is an issue with Go 1.7 and Windows 2016 SYMLINKD directories which causes Docker volumes and host mounts to fail. +# This registry build uses a custom build of Go (@a2bd5c5) from the master branch to fix - https://github.com/golang/go/issues/15978 + +FROM sixeyed/golang:windowsservercore +SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';"] + +CMD .\go get github.com/docker/distribution/cmd/registry ; ` + cp \"$env:GOPATH\bin\registry.exe\" c:\out\ ; ` + cp \"$env:GOPATH\src\github.com\docker\distribution\cmd\registry\config-example.yml\" c:\out\config.yml + diff --git a/windows/registry/README.md b/windows/registry/README.md new file mode 100644 index 0000000..f3a573b --- /dev/null +++ b/windows/registry/README.md @@ -0,0 +1,33 @@ +# Registry Lab + +A registry is a service for storing and accessing Docker images. [Docker Hub](https://hub.docker.com) and [Docker Store](https://store.docker.com) are the best-known hosted registries, which you can use to store public and private images. You can also run your own registry using the open-source [Docker Registry](https://docs.docker.com/registry), which is a Go application that can run in a Windows container. + +## What You Will Learn + +You'll learn how to: + +- build a Docker image which packages the [Docker Registry](https://docs.docker.com/registry) application on Windows Nano Server; + +- run a local registry in a container and configure your Docker engine to use the registry; + +- generate SSL certificates (using Docker!) and run a secure local registry with a friendly domain name; + +- generate encrypted passwords (using Docker!) and run an authenticated, secure local registry over HTTPS with basic auth. + +> Note. The open-source registry does not have a Web UI, so there's no friendly interface like [Docker Hub](https://hub.docker.com) or [Docker Store](https://store.docker.com). Instead there is a [REST API](https://docs.docker.com/registry/spec/api/) you can use to query the registry. For a local registry which has a Web UI and role-based access control, Docker, Inc. has the [Trusted Registry](https://www.docker.com/sites/default/files/Docker%20Trusted%20Registry.pdf) product. + +### Prerequisites + +You'll need Docker running on Windows. You can follow the [Windows Container Lab Setup](https://github.com/docker/labs/blob/master/windows/windows-containers/Setup.md) to install Docker on Windows 10, or Windows 2016 - locally, on AWS and Azure. + +You should be familiar with the key Docker concepts, and with Docker volumes: + +- [Docker concepts](https://docs.docker.com/engine/understanding-docker/) +- [Docker volumes](https://docs.docker.com/engine/tutorials/dockervolumes/) + +## The Lab + +- [Part 1 - Building the Registry Image](part-1.md) +- [Part 2 - Running a Registry Container](part-2.md) +- [Part 3 - Running a Secured Registry Container](part-3.md) +- [Part 4 - Using Basic Authentication with a Secured Registry](part-4.md) diff --git a/windows/registry/part-1.md b/windows/registry/part-1.md new file mode 100644 index 0000000..d36e8a1 --- /dev/null +++ b/windows/registry/part-1.md @@ -0,0 +1,102 @@ +# Part 1 - Building the Registry Image + +Docker provides an [official registry image](https://hub.docker.com/_/registry/) on the Hub, but currently it is only available as a Linux image. The application is written in Go so it can be compiled for Windows and run as a container on Windows 10 and Windows Server 2016. + +> Note. Expect the official image to have a Windows variant soon, but this part of the lab is still useful if you want to build from the latest source code yourself. + +## Building from Source + +The Go source code for the registry is on GitHub in Docker's [distribution](https://github.com/docker/distribution) repository. Building Go applications is simple with the [go command](https://golang.org/doc/articles/go_command.html), we just need to install the Go tools and run: + +```PowerShell +go get github.com/docker/distribution/cmd/registry +``` + +On Windows the build will produce a binary file, `registry.exe`, which is a standalone executable. We can package that exe into a Docker image which doesn't need Go installed, so our Windows registry image will be small and focused. + +We won't install Go and Git on our local machine though, instead we'll use the [Docker Builder pattern](http://blog.terranillius.com/post/docker_builder_pattern/) and package up the toolset into an image we can use to build the registry on any Windows host running Docker. + +## Dockerfile for the Registry Builder + +The Dockerfile for the registry builder is in [Dockerfile.builder](Dockerfile.builder), and it's very simple. It begins in the usual way for Windows Dockerfiles - overriding the escape character and specifying PowerShell to use as the command shell: + +```Dockerfile +# escape=` +FROM sixeyed/golang:windowsservercore +SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';"] +``` + +The base image is [sixeyed/golang](https://hub.docker.com/r/sixeyed/golang/) which is on the Docker Hub, built from [this Dockerfile](https://github.com/sixeyed/dockers-windows/blob/master/golang/Dockerfile). It has the Go toolset installed, but rather than using a released version, it builds Go from the source on GitHub to get the latest features. + +> Note. There is an [official Go image](https://hub.docker.com/_/golang/) on Docker Hub, which has Windows Server Core and Nano Server variants. That uses the latest release of Go - 1.7 - which has [an issue](https://github.com/golang/go/issues/15978) that stops you using Docker volumes on Windows. It's fixed in the latest source code, which is why we use an image that builds from source, but when Go 1.8 is released we can switch to using the official image as the base for the builder. + +There's a single `CMD` instruction to build the latest version of the registry and copy the built files to a known output location: + +```Dockerfile +CMD .\go get github.com/docker/distribution/cmd/registry ; ` + cp \"$env:GOPATH\bin\registry.exe\" c:\out\ ; ` + cp \"$env:GOPATH\src\github.com\docker\distribution\cmd\registry\config-example.yml\" c:\out\config.yml +``` + +When we run a container from the builder image, it will compile the latest registry code, and we can map the output location to a directory on the host to get the application files. + +## Building the Registry Server + +First we build the builder image - all the images in this lab use [microsoft/windowsservercore](https://hub.docker.com/r/microsoft/windowsservercore/) or [microsoft/nanoserver](https://hub.docker.com/r/microsoft/nanoserver/) as the base images, so you can only use a Windows host to build and run them. From a PowerShell session, navigate to the lab folder and build the builder: + +```PowerShell +docker build -t registry-builder -f Dockerfile.builder . +``` + +When you first run this command, Docker will download the `sixeyed/golang` image, which will take a while. When it's done, you can create a directory for the application, and run a builder container to build the application and copy it to the host: + +```PowerShell +mkdir registry +docker run --rm -v $pwd\registry:c:\out registry-builder +``` + +Now you'll have two files in the `.\registry` folder in the lab root: + +- `registry.exe` - the standalone registry server application (around 18MB); +- `config.yml` - a basic configuration file for running a local registry (<1KB). + +These are the files we'll package into a registry server image. + +## Dockerfile for the Registry Server + +The [Dockerfile](Dockerfile) for the registry image is also very simple. It starts in the standard way and uses `microsoft/nanoserver` as the base, because we don't need any of the additional features in `microsoft/windowsservercore`: + +```Dockerfile +# escape=` +FROM microsoft/nanoserver +SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';"] +``` + +Next we set up the integration between the container and the host: + +```Dockerfile +EXPOSE 5000 +ENV REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=c:\\data +``` + +Port 5000 is the default port for the registry, so we make that available from the image, and we specify a value for the `REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY` environment variable. That's where the registry stores all the image layers, and we default to a known location, `c:\data`. + +Finally we copy the applcation files into the working directory and set the startup command: + +```Dockerfile +WORKDIR c:\\registry +COPY ./registry/ . +CMD ["registry", "serve", "config.yml"] +``` + +To build the image, we just need to run `docker build`: + +```PowerShell +docker build -t registry . +``` + +The Docker image for the registry running on Nano Server is 310MB compressed and 830MB uncompressed - of which 810MB is the `microsoft/nanoserver` base layer. + +## Next + +- [Part 2 - Running a Registry Container](part-2.md) \ No newline at end of file diff --git a/windows/registry/part-2.md b/windows/registry/part-2.md new file mode 100644 index 0000000..2cfa15a --- /dev/null +++ b/windows/registry/part-2.md @@ -0,0 +1,153 @@ +# Part 2 - Running a Registry Container + +There are several ways to run a registry container from the image we built in [Part 1](part-1.md). The simplest is to run an insecure registry over HTTP, but for that we need to configure Docker to explicitly allow insecure access to the registry. + +## Testing the Registry Image + +First we'll test that the registry image is working correctly, by running it without any special configuration: + +```PowerShell +docker run -d -p 5000:5000 --name registry registry +``` + +To use the registry we'll need the IP address of the container: + +```PowerShell +> $ip = docker inspect --format '{{ .NetworkSettings.Networks.nat.IPAddress }}' registry +> $ip +172.24.194.96 +``` + +In my case, the container IP address is `172.24.194.96`, yours will be different. We'll use that as the domain part of the image name for pushing and pulling images. + +## Understanding Image Names + +Typically we work with images from the Docker Hub, which is the default registry for the Docker Engine. Commands using just the image repository name work fine, like this: + +```PowerShell +docker pull microsoft/nanoserver +``` + +`microsoft/nanoserver` is the repository name, which we are using as a short form of the full image name. The full name is `docker.io/microsoft/nanoserver:latest`. That breaks down into three parts: + +- `docker.io` - the hostname of the registry which stores the image; +- `microsoft/nanoserver` - the repository name, in this case in `{userName}/{imageName}` format; +- `latest` - the image tag. + +If a tag isn't specified, then the default `latest` is used. If a registry hostname isn't specified then the default `docker.io` for Docker Hub is used. If you want to use images with any other registry, you need to explicitly specify the hostname - the default is always Docker Hub, you can't change to a different default registry. + +With our local registry, the hostname is the IP address, and we also need to specify the custom port we're using. The full registry address is `172.24.194.96:5000`. + +## Configuring Docker for the Insecure Registry + +Docker expects all registries to run on HTTPS. In the next part of this lab we will run a secure version of our registry container, but the current version runs on HTTP. If you try to use it, Docker will give you an error message like this: + +``` +http: server gave HTTP response to HTTPS client +``` + +We need to set up the Docker Engine to explictly use HTTP for our insecure registry. The setting is needed for `dockerd.exe`, which is the Windows version of the Docker engine. In Windows it runs as a Windows Service, so first we need to stop and unregister the service. + +> Note. This will kill all your running containers. + +```PowerShell +Stop-Service docker +dockerd --unregister-service +``` + +Now we'll re-register the service with custom startup options, adding the IP address of the registry container as an insecure registry, where the engine will use HTTP rather than HTTPS: + +``` +dockerd --register-service -G docker -H npipe:// -H 0.0.0.0:2375 --insecure-registry 172.24.194.96:5000 +Start-Service docker +``` + +Docker is running again now, so we can restart the registry container. When we stopped the Docker Windows Service, the container was stopped, but it still exists with the original IP address, so we can just start it again: + +```PowerShell +docker start registry +``` + +The registry is running at the expected address, and we've configured Docker to allow access, so we can push and pull images to our local registry, just like we can with Docker Hub and Docker Store. + +## Pushing and Pulling from the Local Registry + +Docker uses the hostname from the full image name to determine which registry to use. We can buid images and include the local registry hostname in the image tag, or use the `docker tag` command to add a new tag to an existing image. + +These commands pull a public image from Docker Hub, tag it for use in the private registry with the full name `172.24.194.96:5000/labs/hello-world:nanoserver`, and then push it to the registry: + +```PowerShell +docker pull sixeyed/hello-world:nanoserver +docker tag sixeyed/hello-world:nanoserver 172.24.194.96:5000/labs/hello-world:nanoserver +docker push 172.24.194.96:5000/labs/hello-world:nanoserver +``` + +When you push the image to your local registry, you'll see similar output to when you push a public image to the Hub: + +``` +The push refers to a repository [172.24.194.96:5000/labs/hello-world] +d6826c28b1cd: Pushed +2c195a33d84d: Skipped foreign layer +342d4e407550: Skipped foreign layer +nanoserver: digest: sha256:961497c5ca49dc217a6275d4d64b5e4681dd3b2712d94974b8ce4762675720b4 size: 1149 +``` + +> Note. The two layers from Microsoft's base image are skipped - they don't get stored in the local registry, because the image is not freely redistributable. Track [GitHub issue 27580]( https://github.com/docker/docker/issues/27580) to see if a resolution is found. + +On the local machine, you can remove the new image tag and the original image, and pull it again from the local registry to verify it was correctly stored: + +``` +docker rmi 172.24.194.96:5000/labs/hello-world:nanoserver +docker rmi sixeyed/hello-world:nanoserver +docker pull 172.24.194.96:5000/labs/hello-world:nanoserver +``` + +That exercise shows the registry we built works correctly, but at the moment it's not very useful because all the image data is stored in the container's writeable storage area, which will be lost when the container is removed. To store the data outside of the container, we need to mount a host directory when we start the container. + +## Running a Registry Container with External Storage + +Let's get rid of the existing registry container - removing the container also removes its storage layer, so any images you had pushed will be lost: + +```PowerShell +docker kill registry +docker rm registry +``` + +We'll use a host-mounted Docker volume for the new container. When the registry server in the container writes image layer data, it will think it's writing to a local directory in the container but it will actually be writing to a folder on the host. + +We also need to specify the IP address, because we want to use the same IP the previous registry had - that's what our Docker Engine is configured to use, and it's how we've tagged our images. The `-v` option maps the host folder, and the `--ip` option specifies an IP address: + + +```PowerShell +mkdir c:\registry-data +docker run -d -p 5000:5000 --name registry -v c:\registry-data:c:\data --ip 172.24.194.96 registry +``` + +Repeat the previous `docker push` command to upload an image to the registry container, and the layers will be stored in the container's `C:\data` directory, which is actually mapped to the `C:\registry-data` directory on you local machine. The `tree` command will show you the directory structure the registry server uses: + +``` +> &tree c:\registry-data +Folder PATH listing for volume Windows 2016 +Volume serial number is A456-8058 +C:\REGISTRY-DATA +└───docker + └───registry + └───v2 + ├───blobs + │ └───sha256 + │ ├───06 + │ │ └───06162e188174e2f0b76a2fd507645ca13b3beb43204cccddf82dcb0251e34fb4 + │ ├───96 + │ │ └───961497c5ca49dc217a6275d4d64b5e4681dd3b2712d94974b8ce4762675720b4 +... +``` + +Storing data outside of the container means we can build a new version of the registry image and replace the old container with a new one using the same host mapping - so the new registry container has all the images stored by the previous container. + +This container is still limited though. The container is accessible externally using the host's IP address, but that's different from the container's IP address - so you need to use different tags and a different engine configuration for external clients. + +Using an insecure registry also isn't practical in multi-user scenarios. Effectively there's no security so anyone can push and pull images if they know the registry hostname. The registry server supports authentication, but only over a secure SSL connection. We'll run a secure version of the registry server in a container next. + +## Next + +- [Part 3 - Running a Secured Registry Container](part-3.md) \ No newline at end of file diff --git a/windows/registry/part-3.md b/windows/registry/part-3.md new file mode 100644 index 0000000..0eeded3 --- /dev/null +++ b/windows/registry/part-3.md @@ -0,0 +1,154 @@ +# Part 3 - Running a Secured Registry Container + +We saw how to run a simple registry container in [Part 2](part-2.md), using the image we built in [Part 1](part-1.md). The registry server con be configured to serve HTTPS traffic on a known domain, so it's straightforward to run a secure registry for private use with a self-signed SSL certificate. + +## Generating the SSL Certificate + +The Docker docs explain how to [generate a self-signed certificate](https://docs.docker.com/registry/insecure/#/using-self-signed-certificates) on Linux using a command like this: + +``` +openssl req \ +-newkey rsa:4096 -nodes -sha256 -keyout certs/domain.key \ +-x509 -days 365 -out certs/domain.crt +``` + +[OpenSSL](https://www.openssl.org/) is a very popular TLS/SSL toolkit in Linux, but it's less common in Windows. There is a Windows build hosted at [indy.fulgan.com](https://indy.fulgan.com/SSL/), but rather than install it onto our Windows host to run a one-off command, we can use a Docker image with OpenSSL installed. + +The [sixeyed/openssl](https://hub.docker.com/r/sixeyed/openssl/) image on Docker Hub is built from this [Dockerfile](https://github.com/sixeyed/dockers-windows/blob/master/openssl/Dockerfile), which just installs and configures OpenSSL on top of the Windows Nano Server base image. + +We can use it to generate the SSL certificate for the registry with Docker: + +```PowerShell +mkdir certs +docker run -it --rm -v $pwd\certs:c:\certs sixeyed/openssl:nanoserver ` + req -newkey rsa:4096 -nodes -sha256 -x509 -days 365 ` + -keyout c:\certs\registry.local.key -out c:\certs\registry.local.crt ` + -subj '/CN=registry.local/O=sixeyed/C=GB' +``` + +This will create a certificate file `registry.local.crt`, and a private key file `registry.local.key` in the `certs` subdirectory of the working path. The `-subj` option specifies the details for the domain. This example uses `registry.local` for the common name, which needs to match the hostname address for the registry server. The organization name and country aren't used, but need to be valid values. + +Now we have an SSL certificate, we can run a secure registry. + +## Running the Registry Securely + +The registry server supports several configuration switches as environment variables, including the details for running securely. We can use the same image we've already used, but configured for HTTPS. + +If you have an insecure registry container still running from [Part 2](part-2.md), remove it: + +```PowerShell +docker kill registry +docker rm registry +``` + +For the secure registry, we need to run a container which has the SSL certificate and key files available, which we'll do with an additional volume mount (so we have one volume for registry data, and one for certs). We also need to specify the location of the certificate files, which we'll do with environment variables: + +```PowerShell +docker run -d -p 5000:5000 --name registry ` + -v c:\registry-data:c:\data -v $pwd\certs:c:\certs ` + -e REGISTRY_HTTP_TLS_CERTIFICATE=c:\certs\registry.local.crt ` + -e REGISTRY_HTTP_TLS_KEY=c:\certs\registry.local.key ` + registry +``` + +The new parts to this command are: + +- `-v $pwd\certs:c:\certs` - mount the local `certs` folder into the container, so the registry server can access the certificate and key files; +- `-e REGISTRY_HTTP_TLS_CERTIFICATE` - specify the location of the SSL certificate file; +- `-e REGISTRY_HTTP_TLS_KEY` - specify the location of the SSL key file. + +We'll let Docker assign a random IP address to this container, because we'll be accessing it by host name. The registry is running securely now, but we've used a self-signed certificate for an internal domain name, so we need to set up Windows to find the host and trust the certificate. + +## Configuring Windows to Access the Registry + +The Docker client uses the security of the host operating system when it accesses an HTTPS registry. Windows doesn't trust self-signed certificates because they weren't created by a trusted Certificate Authority, so Windows will block access to our secure registry. + +In our network we can trust our own certificate, but we'll need to add it to the certificate store in every Windows client machine we want to use with the registry. The `registry.local.crt` file we generated is the public certificate which can be safely distributed - the `registry.local.key` file is the private key which should be kept secure. + +These PowerShell commands will install the certificate onto the Windows host: + +```PowerShell +$cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2 ` + $pwd\certs\registry.local.crt +$store = new-object System.Security.Cryptography.X509Certificates.X509Store('Root','localmachine') +$store.Open('ReadWrite') +$store.Add($cert) +$store.Close() +``` + +> Note. This installs the self-signed certificate as a trusted CA for all users of the machine. If the private key for your cert is compromised then an attacker could exploit the trust you set up here, by signing malicious services with your certificate. + +If you want to use the registry from other Windows machines, you'll need to distribute the `.crt` fike (**not** the `.key` file), and install the certificate on all client machines. + +The next step is to add a DNS entry for the `registry.local` hostname to point to the container's IP address. The easiest way to do that is by adding an entry to the [hosts](https://en.wikipedia.org/wiki/Hosts_(file)) file: + +```PowerShell +Add-Content -Path 'C:\Windows\System32\drivers\etc\hosts' "$ip registry.local" +``` + +Remote machines will be able to access the registry container from your Docker host, because we publish the port when the container starts - but the hosts entry will be for the IP address of the machine, not the container. + +## Accessing the Secure Registry + +We're ready to run a secure registry now. We want a reliable service, so we'll remove the existing container and start a new one with some more options: + +```PowerShell +docker kill registry +docker rm registry + +docker run -d -p 5000:5000 --name registry ` + --ip $ip --restart unless-stopped ` + -v c:\registry-data:c:\data -v $pwd\certs:c:\certs ` + -e REGISTRY_HTTP_TLS_CERTIFICATE=c:\certs\registry.local.crt ` + -e REGISTRY_HTTP_TLS_KEY=c:\certs\registry.local.key ` + registry +``` + +The new parts here are: + +- `--ip $ip` - specify an explicit IP adress. We're using the IP from the previous container, which is what the address we've mapped to the `registry.local` domain name in the `hosts` file; +- `--restart unless-stopped` - restart the container when it exits, unless it has been explicity stopped. When the host restarts, Docker will start the registry container, so it's always available. + +Now we have a domain name for our registry, the image tags are a lot more flexible - we don't need a specific IP address in the image name. We still tag, push and pull images in the same way: + +```PowerShell +docker tag sixeyed/hello-world:nanoserver registry.local:5000/labs/hello-world +docker push registry.local:5000/labs/hello-world +``` + +> Note. You can use any valid DNS name for your registry hostname but there must be at least one period in the name - if there's no period then Docker can't distinguish the hostname part from the repository name. The hostname you use has to match the common name you used when you generated the SSL certificate. + +The IP address for my Docker host is `192.168.2.196`, so on a *different* machine I can map the registry hostname to that address by writing to the host file, and install the certificate which I've copied locally: + +```PowerShell +Add-Content -Path 'C:\Windows\System32\drivers\etc\hosts', "192.168.2.196 registry.local" + +$cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2 ` + c:\drops\registry.local.crt +$store = new-object System.Security.Cryptography.X509Certificates.X509Store('Root','localmachine') +$store.Open('ReadWrite') +$store.Add($cert) +$store.Close() +``` + +And from that machine (with Docker installed), I can pull the image from the registry container running on the remote machine on my local network: + +```PowerShell +> docker pull registry.local:5000/labs/hello-world +Unable to find image 'registry.local:5000/labs/hello-world:latest' locally +latest: Pulling from labs/hello-world + +5496abde368a: Already exists +94b4ce7ac4c7: Pull complete +06162e188174: Pull complete +Digest: sha256:961497c5ca49dc217a6275d4d64b5e4681dd3b2712d94974b8ce4762675720b4 +Status: Downloaded newer image for registry.local:5000/labs/hello-world:latest +``` + +In this case, the client machine already had one of the Windows Nano Server base layers (`5496a`), but it pulled an update layer from Docker Hub (`94b4c`), and it pulled the custom layer for my image from my own registry (`06162`). + +We can go one step further with the open-source registry server, and add basic authentication - so we can require users to securely log in to push and pull images. + +## Next + +- [Part 4 - Using Basic Authentication with a Secured Registry](part-4.md) \ No newline at end of file diff --git a/windows/registry/part-4.md b/windows/registry/part-4.md new file mode 100644 index 0000000..ab01d82 --- /dev/null +++ b/windows/registry/part-4.md @@ -0,0 +1,114 @@ +# Part 4 - Using Basic Authentication with a Secured Registry + +From [Part 3](part-3.md) we have a registry running in a Docker container, which we can securely access over HTTPS from any machine in our network. We used a self-signed certificate, which has security implications, but you could buy an SSL from a CA instead, and use that for your registry. With secure communication in place, we can set up user authentication. + +## Usernames and Passwords + +The registry server and the Docker client support [basic authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) over HTTPS. The server uses a file with a collection of usernames and encrypted passwords. The file uses a common standard from the Linux world - [Apache htpasswd](https://httpd.apache.org/docs/current/programs/htpasswd.html), but as usual we don't want to install Apache on our local machine to use one tool. + +On the Docker Hub, the repository [sixeyed/httpd](https://hub.docker.com/r/sixeyed/httpd/) ([Dockerfile](https://github.com/sixeyed/dockers-windows/blob/master/httpd/Dockerfile)) is configured with Apache and the associated tools already installed. We can use that image to run `htpasswd` and generate the encrypted strings. These commands create a new `auth` subdirectory and a new `registry.htpasswd` file with one set of credentials: + +```PowerShell +mkdir auth +$creds = docker run --rm sixeyed/httpd:windowsservercore htpasswd -b -n -B elton d0cker +Add-Content -Path .\auth\registry.htpasswd $creds[0] +``` + +> Note. The output from `htpasswd` contains additional whitespace which we need to remove - we only write the first line of output in the text file. + +The [htpasswd options](https://httpd.apache.org/docs/current/programs/htpasswd.html) will create the encrypted password in a suitable format for the registry server: + +- `-b` - use bcrypt encryption +- `-n` - read username and password from the command line +- `elton` - username +- `d0cker` - password. + +To add new users, repeat the command to append a new entry to the existing file. This adds a new user `francis`, with password `r3gL4b`: + +```PowerShell +$creds = docker run --rm sixeyed/httpd:windowsservercore htpasswd -nb -B francis r3gL4b +Add-Content -Path .\auth\registry.htpasswd $creds[0] +``` + +We can verify the entries have been written by checking the file contents - which should show the usernames in plain text and a cipher text password: + +```PowerShell +> cat .\auth\registry.htpasswd +elton:$2y$05$saIVQ.pV.9EPOsLpNP04puj0Mf9r2wMHeGg/XViGhPfdoxb1oaCPO +francis:$2y$05$CWobimW8aoKSCLf4cp9lGulzAXxPfUIqc452PdArxPfyK8zPEOG9a +``` + +## Running an Authenticated Secure Registry + +Adding authentication to the registry is a similar process to adding SSL - we need to run the registry with access to the `htpasswd` file on the host, and configure authentication using environment variables. + +As before, we'll remove the existing container and run a new one with authentication configured: + +```PowerShell +docker kill registry +docker rm registry + +docker run -d -p 5000:5000 --name registry ` + --ip $ip --restart unless-stopped ` + -v c:\registry-data:c:\data -v $pwd\certs:c:\certs -v $pwd\auth:c:\auth ` + -e REGISTRY_HTTP_TLS_CERTIFICATE=c:\certs\registry.local.crt ` + -e REGISTRY_HTTP_TLS_KEY=c:\certs\registry.local.key ` + -e REGISTRY_AUTH=htpasswd ` + -e REGISTRY_AUTH_HTPASSWD_REALM='Registry Realm' ` + -e REGISTRY_AUTH_HTPASSWD_PATH=c:\auth\registry.htpasswd ` + registry +``` + +The new options for this container are: + +- `-v $pwd\auth:c:\auth` - mount the local `auth` folder into the container, so the registry server can access `htpasswd` file; +- `-e REGISTRY_AUTH=htpasswd` - use the registry's `htpasswd` authentication method; +- `-e REGISTRY_AUTH_HTPASSWD_REALM='Registry Realm'` - specify the authentication realm; +- `-e REGISTRY_AUTH_HTPASSWD_PATH=c:\auth\registry.htpasswd` - specify the location of the `htpasswd` file. + +Now the registry is using secure transport and user authentication. + +## Authenticating with the Registry + +With basic authentication, users cannot push or pull from the registry unless they are authenticated. If you try and pull an image without authenticating, you will get an error: + +```PowerShell +> docker pull registry.local:5000/labs/hello-world +Using default tag: latest +Error response from daemon: Get https://registry.local:5000/v2/labs/hello-world/manifests/latest: no basic auth credentials +``` + +The result is the same for valid and invalid image names, so you can't even check a repository exists without authenticating. Logging in to the registry is the same `docker login` command you use for Docker Hub, specifying the registry hostname: + +```PowerShell +> docker login registry.local:5000 +Username: elton +Password: +Login Succeeded +``` + +If you use the wrong password or a username that doesn't exist, you get a `401` error message: + +``` +Error response from daemon: login attempt to https://registry.local:5000/v2/ failed with status: 401 Unauthorized +``` + +Now you're authenticated, you can push and pull as before: + +```PowerShell +> docker pull registry.local:5000/labs/hello-world +Using default tag: latest +latest: Pulling from labs/hello-world +Digest: sha256:961497c5ca49dc217a6275d4d64b5e4681dd3b2712d94974b8ce4762675720b4 +Status: Image is up to date for registry.local:5000/labs/hello-world:latest +``` + +> Note. The open-source registry does not support the same authorization model as Docker Hub or Docker Trusted Registry. Once you are logged in to the registry, you can push and pull from any repository, there is no restriction to limit specific users to specific repositories. + +## Conclusion + +[Docker Registry](https://docs.docker.com/registry/) is a free, open-source application for storing and accessing Docker images. You can run the registry in a container on your own network, or in a virtual network in the cloud, to host private images with secure access. For Linux hosts, there is an [official registry image](https://hub.docker.com/_/registry/) on Docker Hub, but in this lab we saw how to build and run the registry from the lastest source code, in a Windows container. + +We've covered all the options, from running an insecure registry, through adding SSL to encrypt traffic, and finally adding basic authentication to restrict access. By now you know how to set up a usable registry in your own environment, and you've also used some key Docker patterns - using containers as build agents and to run basic commands, without having to install software on your host machines. + +There is still more you can do with Docker Registry - using a different [storage driver](https://docs.docker.com/registry/storage-drivers/) so the image data is saved to reliable share storage, and setting up your registry as a [caching proxy for Docker Hub](https://docs.docker.com/registry/recipes/mirror/) are good next steps. \ No newline at end of file From 641ec829d5ba3ccd41912ff6a45635bbc84c0103 Mon Sep 17 00:00:00 2001 From: Elton Stoneman Date: Mon, 28 Nov 2016 15:29:20 +0000 Subject: [PATCH 2/3] Added registry Windows lab Signed-off-by: Elton Stoneman --- windows/readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/windows/readme.md b/windows/readme.md index 2aa054d..5284a38 100644 --- a/windows/readme.md +++ b/windows/readme.md @@ -6,3 +6,4 @@ We have three Windows and .NET tutorials * [Beginning ASP.NET Web application](aspnet-web/README.md) * [Hello Dotnetbot App](hello-dotnetbot/README.md) * [SQL Server Database](sql-server/README.md) +* [Run a local Docker Registry](registry/README.md) From 5fe24549f4e1ef275676e45bfe4d5ccb0f9b4cc1 Mon Sep 17 00:00:00 2001 From: Elton Stoneman Date: Mon, 28 Nov 2016 20:53:04 +0000 Subject: [PATCH 3/3] Updated GH issue for base layers Signed-off-by: Elton Stoneman --- windows/registry/part-2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/registry/part-2.md b/windows/registry/part-2.md index 2cfa15a..9b7648d 100644 --- a/windows/registry/part-2.md +++ b/windows/registry/part-2.md @@ -92,7 +92,7 @@ d6826c28b1cd: Pushed nanoserver: digest: sha256:961497c5ca49dc217a6275d4d64b5e4681dd3b2712d94974b8ce4762675720b4 size: 1149 ``` -> Note. The two layers from Microsoft's base image are skipped - they don't get stored in the local registry, because the image is not freely redistributable. Track [GitHub issue 27580]( https://github.com/docker/docker/issues/27580) to see if a resolution is found. +> Note. The two layers from Microsoft's base image are skipped - they don't get stored in the local registry, because the image is not freely redistributable. Check [GitHub issue 27580]( https://github.com/docker/docker/issues/27580) for more information. On the local machine, you can remove the new image tag and the original image, and pull it again from the local registry to verify it was correctly stored: