How to properly install certificates on Docker in ASP.NET Core application?
Asked Answered
F

1

8

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?

Fanny answered 6/12, 2019 at 0:24 Comment(4)
After coming back to this question, I've learned that you typically use Kubernetes for doing this stuff...but it would be really nice if someone can provide an answer that provides a lot more context so others can refer; also, docker is really like a stepping stone to kubernetes and pods, etc., but again, someone who can provide direction here would be most helpful.Fanny
One way I have passed certificates to the container is by making the certificate an embedded resource and reading from my resources in Program.cs. I don't think this is a good industry best-practice, especially for organizations with dedicated infrastructure teams who manage secrets (e.g., certificates). I've also seen ways of passing certs in MSFT's examples for docker and docker-compose.Fanny
Given the comment above, how does one access those resources within the container? Do we need to write a .sh script that installs and updates the certificate, and then pull the certificate by Subject Name (or some other method to read from the cert store)?Fanny
Linux only supports StoreLocation.CurrentUser at this time. See learn.microsoft.com/en-us/dotnet/standard/security/…Selfless
M
3

One solution I found was as follows:

  1. Mount the certificate directory in your 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:

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                    //no longer needed if you configure via 
                    //kestrel options outside of c#
                    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.

-- Additional Note If you are having problems, please review your mount points/volume configuration.

I did this on a mac recently and had to update my certs as follows:

 - ~/.aspnet/https:/home/app/.aspnet/https:ro
Mazza answered 8/8, 2020 at 1:12 Comment(2)
What I struggle with regarding volumes is mapping the path on my Debian to the path in my container. I don't run docker-compose on my local machine. I run it on my server. Is ~/.aspnet/https somewhere I set up on my server?Ventura
Everything on the left part of the : (colon) is what is on your machine. Everything on the right is what exists on the container itself. If you aren't sure about what lives in the container, try building a random container, such as an nginx container or something and playing around with that. If you have .NET installed on your Debian machine, there should be a hidden folder, ~/.aspnet on your machine and that should have an https folder there. Check by just doing ls ~/.aspnet/.Mazza

© 2022 - 2024 — McMap. All rights reserved.