Tag Archives: SSL

Configuring Docker to use a Dev Cert When Calling out to the Host Machine

I’ve recently being wrestling with trying to get an ASP.Net service within a docker container to call a service running outside of that container (but on the same machine). As you’ll see as we get further into the post, this is a lot more difficult that it first appears. Let’s start with a diagram to illustrate the problem:

The diagram above illustrates, at a very basic level, what I was trying to achieve. Essentially, have the service running inside docker call to a service outside of docker. In real life, the service (2) would be remote: very likely on a different physical server, and definitely have an allocated domain address; however, for this experiment, it lives on the same physical (or virtual) machine as the docker host.

Before I continue, I must point out that the solution to this comes by way of some amazing help by Rob Richardson: he gave a talk at NDC Porto that got be about 70% of the way there, and helped me out further to actually get this working!

Referencing a Service outside of Docker from within Docker

Firstly, let’s consider a traditional docker problem: if I load Asp.Net Service (2) then I would do so in a browser referencing localhost. However, if I reference localhost from within docker, that refers to the localhost of the container, not the host machine. The way around this is with host.docker.internal: this gives you a path to the host machine of the docker container.

Certificates – The Problem

Okay, so onto the next (and main) issue: when I try to call Asp.Net Service (2) from the docker container, I get an SSL error:

The remote certificate is invalid according to the validation procedure: RemoteCertificateNameMismatch, RemoteCertificateChainErrors

Why

The reason has to do with the way that certificates work; and, in some cases, don’t work. Firstly, if you watch the linked video, you’ll see that the dev-cert functionality in Linux has a slight flaw – in that it doesn’t do anything*. Secondly, because you’re jumping (effectively) across machines here, you can’t just issue a dev cert to each anyway, as it will be a different dev cert; and thirdly, dev-certs are issues to localhost by default: but as we saw, we’re actually trying to contact host.docker.internal.

Just to elaborate on the trust chain; let’s consider the following diagram:

In this diagram, Certificate A is based on the Root Certificate – if the machine trusts the root certificate, then it will trust Certificate A – however, the same machine will only trust Certificate B if it is explicitly told to do so.

This means that the dev cert for the container will not be trusted on the host, and vice-versa – as neither have any trust chain and relationship – this is the problem, but it’s also the solution.

Okay, so that’s the why – onto the how…

mkcert

Let’s start with introducing mkcert – this is an incredibly useful tool that hugely simplifies the whole process; it can be installed via chocolatey:

choco install mkcert

If you don’t want to use Chocolatey, then the repo is here.

Essentially, what this allows us to do is to create a trusted root certificate, from which, we can base our other certificates. So, once this is installed, we can create a new trusted root certificate like this:

mkcert -install

This installs our trusted root certificate; which we can see here:

This will also generate the following files (on Windows, these will be in %localappdata%\mkcert):

rootCA.pem
rootCA-key.pem

These are the root certificates, so the next thing we need is a certificate that covers the specific domain. You can do that by simply calling mkcert with the appropriate domain(s):

mkcert localhost host.docker.internal

This creates a valid cert for both localhost and host.docker.internal:

localhost.pem
localhost-key.pem

You may wish to rename these to be something slightly more descriptive, but for the purpose of this post, this is sufficient.

Docker

Almost there now – we have our certificates, but we need to copy them to the correct location. Because we’ve run mkcert -install the root certificate is already on the local machine; however, we now need that on the docker client (Asp.Net Service (1) from the diagram above). Firstly, let’s download a mkcert.exe from here for the relevant version of Linux that you’re running.

Let’s copy both the rootCA.pem and rootCA-key.pem into our Asp.Net Service (1) project and then change the dockerfile:

. . .
FROM base AS final
WORKDIR /app
COPY mkcert /usr/local/bin
COPY rootCA*.pem /root/.local/share/mkcert/
RUN chmod +x /usr/local/bin/mkcert \
  && mkcert -install \
  && rm -rf /usr/local/bin/mkcert 
COPY --from=publish /app/publish .
. . .

A few things to mention here:

1. The rest of this file is from the standard Ast.Net docker file. See this post for possible modifications to that file.
2. Each time you execute a RUN command docker makes a temporary image, hence why combining three lines (on line 7) with the && makes sense.
3. When you run the mkcert -install it will pick up the root certificate that you copy into the /root/.local/share/mkcert.
4. Make sure that these lines apply to the runtime version of the image, and not the SDK version (there’s absolutely no point in adding a certificate to the SDK version).
5. The last line (rm -rf /usr/local/bin/mkcert) just cleans up the mkcert files.

The Service

The final part is to copy the generated certificates (localhost.pem and localhost-key.pem) over to the service application (Asp.Net Service (2)). Finally, in the appsettings.json, we need to tell Kestrel to use that key:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Kestrel": {
    "Certificates": {
      "Default": {
        "Path": "localhost-host.pem",
        "KeyPath": "localhost-host-key.pem"
      }
    }
  }
}

That’s it! If you open up the Asp.Net Service (2), you can check the certificate, and see that it’s based on the mkcert root:

References and Acknowledgements

As I said at the start, this video and Rob himself helped me a lot with this – so thanks to him!

It’s also worth mentioning that without mkcert this process would be considerably more difficult!

Footnotes

* actually, that’s not strictly true – Rob points out in his video the nuance here; but the takeaway is that it’s unlikely to be helpful

Create and link a custom domain to an Azure App Service

I’ve recently been playing around with Cloudflare workers. As part of this, I wanted to experiment using them against an Azure App Service, but to do this, you need a custom domain. In this post, I’ll cover how to create a new domain, and then how to register that against an app service and add a TLS certificate, all without leaving the Azure Portal.

Register a New Domain

To register a new domain, you need to select the App Service Domains resource:

In the App Service Domains, you can create a new domain:

Once you’ve created the domain, you can register it against the App Service.

Register the Domain Against an Azure App Service

This assumes that you have an App Service to register against a domain. The example that I’m using here is just a new templated MVC app that was deployed directly to Azure.

Go into the Azure App Service and select Custom domains:

Inside the Custom domains blade, select Add custom domain. As shown in the diagram above, you’ll be asked to enter the domain, and to validate that domain. You’ll then select Add custom domain.

That’s actually all there is to it; you’ve now registered the domain against the app service. However, if you try to navigate to the domain, you’ll see that it doesn’t have a valid certificate – looking in the Custom domains blade, we can see why:

Let’s now add a certificate. This used to be a process that involved a lot of faffing about, and it had been greatly simplified.

To Add a TLS Certificate

Select the TLS / SSL settings blade:

Then select Create AppService Managed Certificate, as shown in the diagram above. This should present you with something similar to the following:

Click Create and this will create the certificate for you. This might be a good point to go and get a brew, as it takes a fair while.

When it’s finally finished, you’ll see something like this:

The last step is to register the certificate with the domain.

Register the Certificate with the Domain

We’re on the home straight now. Go back to Custom Domains:

As you can see, the domain is showing as Not Secure. Select Add Binding (as shown above), and the following dialog (or something similar) should appear:

If you now select Add Binding that should register the certificate against your domain.

References

https://www.youtube.com/watch?v=bXP6IvNYISw