I've been digging around the net and Stackoverflow and I've had some trouble solving a problem I have.
I am trying to standup my ASP.NET Core application into Docker. I have the following cert, let's call it, "FooCert.pfx". I have a copy of FooCert.pfx as a .PEM file as well (FooCert.pem). I'm trying to get my application to find the certificate at runtime. I have a docker-compose.yml file that builds and starts the container; I have some environment variables that link to where the certs are located on the Windows Host; and lastly, I have a DockerFile that wraps the behavior expected by my app.
My application throws an exception when it tries to read from the cert store on the linux container. It says that it can't find the certificate and the store is not recognized. Here are the relevant lines in my dockerfile:
FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build
WORKDIR /src
...
COPY ./FooCert.pem /etc/ssl/certs/FooCert.pem
COPY ./FooCert.pem /usr/local/share/ca-certificates/FooCert.pem
COPY ./FooCert.pfx /usr/local/share/ca-certificates/FooCert.pfx
RUN openssl pkcs12 -in /usr/local/share/ca-certificates/FooCert.pfx -nocerts -nodes | sed -ne '/-BEGIN PRIVATE KEY-/,/-END PRIVATE KEY-/p' > FooCert.key
RUN openssl pkcs12 -in /usr/local/share/ca-certificates/FooCert.pfx -clcerts -nokeys | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > FooCertClientcert.cer
RUN openssl pkcs12 -in /usr/local/share/ca-certificates/FooCert.pfx -cacerts -nokeys -chain | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > FooCertcacerts.cer
COPY ./FooCert.pem /etc/ssl/certs/FooCert.pem
COPY ./FooCert.pem /usr/local/share/ca-certificates/FooCert.pem
RUN ls /usr/local/share/ca-certificates
RUN ls /etc/ssl/certs
RUN update-ca-certificates
...
[code to expose ports, define entrypoint, etc here]
I understand that linux doesn't have the same cert stores as Windows, and I have accounted for that in my codebase. I've tried opening the Root and CertificateAuthority stores with CurrentUser and LocalMachine like so:
var certStore = new X509Store(StoreName.CertificateAuthority, StoreLocation.LocalMachine);
This throws an exception on the Linux container, but has no issue on Windows.
I've also read that it isn't a good practice to expose your certs in your container for security purposes.
TL;DR: What is the recommended practice for storing certificates in containers; and how do I properly access/find those certificates from the store on the linux container in ASP.NET Core?
A custom certificate is configured by creating a directory under /etc/docker/certs.
By default, Docker runs on port 80 with ASP.NET Core, but you can override that. In the example below, the Kestrel server that will run in the container is being configured to listen on port 5000. The other environment variable is simply specifying our environment, which is development in this case.
One solution I found was as follows:
docker-compose.yml
:version: '3.6'
services:
dockertemplate:
image: ${DOCKER_REGISTRY-}dockertemplate
build:
context: .
dockerfile: DockerTemplate/Dockerfile
ports:
#bind [host port]:[container port]
- 8000:80
- 5051:443
- 5050:5050
environment:
- "ASPNETCORE_URLS=https://+;http://+"
- Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx
- Kestrel__Certificates__Default__Password=changeit
volumes:
- ~/.aspnet/https:/https:ro #this is very important
My Dockerfile:
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["DockerTemplate/DockerTemplate.csproj", "DockerTemplate/"]
RUN dotnet restore "DockerTemplate/DockerTemplate.csproj"
COPY . .
WORKDIR "/src/DockerTemplate"
RUN dotnet build "DockerTemplate.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "DockerTemplate.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "DockerTemplate.dll"]
And in your Program.cs
:
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.UseKestrel(options => options.ListenAnyIP(5050, listenOptions => listenOptions.UseHttps(
adapterOptions =>
{
adapterOptions.ServerCertificate = new X509Certificate2("/https/aspnetapp.pfx", "changeit");
})));
});
Make sure that you have configured sharing the necessary permissions for sharing the directory you've chosen to grant access. If you aren't seeing the configured directory on your system, be sure to restart your computer. You can try restarting your docker instance, but restarting my computer did it for me.
Please note: you'd probably want to use Kubernetes for your solution. This is a minimal solution using docker-compose. This is merely one approach you can take.
-- Additional Edit
I learned recently that you can completely remove the .UseKestrel()
section. ASP.NET will hook up the application for you automatically and you can just run the app on port :443/:80 in the container. Ideally you should decouple certificates and SSL from your container. A certificate is a "secret", i.e. it is something that requires it to be securely stored, so you can just port forward from 80 -> your desired http port and 443 -> your desired https port. The launchSettings.json (properties folder in project) is where you would setup the appropriate kestrel options, similar to how the docker-compose.yml is setup.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With