skip to Main Content

How to configure an SSL certificate for ASP.NET Core in a Docker container?

I have a Visual Studio solution with multiple projects that I run on Docker Desktop; this is a "sandbox" environment for the purpose of evaluating code. This is not and never will be a "production" environment. So, the intent is to keep the configuration as simple as possible so I can focus on "real" work. (FWIW at some point I will need to redo this for production… but that is a different problem for another time).

The basic visual studio Docker container has a functioning SSL certificate: eg open https://localhost:10433 in a web browser, the web browser reports the connection as secure with a valid certificate.

Running two docker containers

  • service01 (hostname service01.local)
  • service02 (hostname service02.local)

with container service02 accessing service01 over https.

The problem:

  • accessing the https port on service01 from host using https://localhost:10433 works.
  • accessing the https port on service01 from host using https://service01.local raises certificate invalid warning (which you can override in browser)
  • accessing the https port on service01 from service02 using https://service01.local fails… (see curl response below)
from host
> ping service01
pinging service01.local [172.26.13.76]
reply from 172.26.13.76 
etc

from service02
> ping service01
pinging service01.local [172.26.13.76]
reply from 172.26.13.76 
etc

from service02
> curl https://service01.local
curl: (60) schannel: SEC_E_UNTRUSTED_ROOT (0x80090325) - The certificate chain was issued by an authority that is not trusted.

This all makes sense:

  • https://localhost:port works as the certificate is trusted by Host.
  • https://service01.local certificate is not trusted by browser because certificate CN=localhost not CN=service01.local
  • service02 curl https://service01.local certificate is not trusted because service02 has not been told to trust it AND the CN is wrong

Basically, I need to get a new self signed certificate with CN=service01.local onto the service01 container.

I assume I need to do the following:

  • on host, generate a new self-signed certificate with CN=service01.local
  • on create service01 container, push this certificate into container (via Dockerfile or mount point)
  • on service01 container, "load" certificate for ASP.Core application to use
  • on service02 container, "trust" the certificate used on service01 container

As I noted: this is for a "sandbox" non-production environment, so something quick and dirty is fine (ie I want to avoid compose, kubernetics, reverse proxy, lets encrypt approaches; those tools just add another layer of complexity that I don’t want to deal with right now).

Some of the relevant stackoverflow posts that I have reviewed so far

UPDATE 1: one of the SO posts uses a powershell script to create and register a self-signed cert. Which looks great, except the base aspnet images dont include powershell from what I can tell. Installing powershell image over aspnet image is rabbit hole i dont need to go down.

UPDATE 2:
Using httpclienthandler to ignore ssl errors works for the httpclient, but didnt work for IdentityServer (token invalid error because authority must use https). Basically, Identity Server barfs if SSL / HTTPS connection is not valid. So really need a proper "certificate" for the container.

Reviewing the Microsoft doc on config https on docker, was not useful. basically confirmed that is how Visual Studio is configuring the dockerfile to run OOB. I have a valid dev-cert and it is being loaded to the container. I can confirm this by accessing the container with https://localhost:10433; the url is flagged as secure with the correct dev-cert. if I access the container with https://service01.local instead, the connection is NOT secure as "the certificate for this site is not valid".

A "single" docker container does handle https properly using the localhost port mapping (eg https://localhost:10433); it does not handle https property using the container hostname (eg https://service01.local). Using "multiple" docker containers is just an exasperates the problem.

UPDATE 3:
I gave up on the docker approach and went back to testing using https server (Kestrel). I switched all URL references to use the https://localhost:portnbr instead of https://containername. At which point all of the SSL functionality worked, as expected.

Basically, this means I cant use docker as part of the dev process, except for the most trivial of applications. But I have learned a lot more about the ins and outs of using docker.

UPDATE 4:
this error is off topic for this post, but… the question was asked.

Error communicating from secure web api (service02) to identity service (service01) is really, really weird. this is the code for the webapplication build.

builder.Services.AddAuthentication("Bearer")
    .AddIdentityServerAuthentication("Bearer", options =>
    {
        options.ApiName = "myApi";

// FAIL: using container hostname
        //options.Authority = "https://service01.local";      
        
// FAIL: HTTPS service option
        //options.Authority = "https://localhost:52433";        

// PASS: HTTPS service with http access
// NOTE: if using http then must turn off require https
        options.Authority = "http://localhost:52080";           
        options.RequireHttpsMetadata = false;                   
// NOTE: turn off require https metadata for testing only
    });
builder.Services.AddAuthorization();
  • using docker with https://service.local FAILs as per previous.
  • using docker with localhost:port FAILs > certificate invalid as container hostname is not included in certificate as Subject Alternative Name
  • using https with https://localhost:52433 fails > connection actively refused by host
  • using https with using http://localhost:52080 works > service 1 has app.UseHttpsRedirection(); included in http request pipeline.

now for the weird part. when I call https://localhost:52443/.well-known/openid-configuration from another app (service03) the https connection works. so why does setting the identity server Authority to https fail with "connection actively refused", yet using the same url from a web browser or a different service app or switching to http works?

I ran out of coffee trying to figure this one out. I really gave up, once I read the commercial licensing costs for the supported version Identity Server. Chalk this up as a good learning exercise.

UPDATE 5: creating self signed certs…
as per qiang-fu answer below, I need to create the appropriate certs.
here is the powershell script I am using to do so…

# Generate Certificate using OpenSSL
# Assume Git is installed, which includes OpenSSL client

write-host "STARTING GEN CERTIFICATE"

$outputFile = (Get-Item .).FullName
write-host "Working Directory: " $outputFile

# NOTE: single '' is used by powershell, versus "" used by OS
$openssl = 'C:Program FilesGitusrbinopenssl.exe'
write-host "OpenSSL Executable: " $openssl

# UPDATE THIS: step options: 1-7, CA, SR, ALL
$step = 7

# UPDATE THIS: hostname to generate certificate for
$hostname = "service02"

# local variables
$caKey = "ca.key"
$caCert = "ca.crt"
$serverKey = "$hostname.key"
$serverCsr = "$hostname.csr"
$serverCert = "$hostname.crt"
$serverPfx = "$hostname.pfx"

# UPDATE THIS: certificate details
$certSubj = '"/C=CA/ST=BritishColumbia/L=Vancouver/O=MyCo/OU=MyCo/CN=MyCo.local"'
$certExt = '"subjectAltName=DNS:myco.local"'

$serverSubj = "/C=CA/ST=BritishColumbia/L=Vancouver/O=MyCo/OU=MyCo/CN=$hostname.local"
$serverExt = "subjectAltName=DNS:$hostname.local"

# show settings
if (($step -eq 0) -or ($step -eq "CA") -or ($step -eq "SR") -or ($step -eq "ALL"))
{
    write-host "Settings..."
    write-host "step $step"
    write-host $hostname
    write-host $caKey $caCert
    write-host $serverKey $serverCsr $serverCert
    write-host $serverExt
    
# TEST command will execute
    #& $openssl
    #& $openssl help
}

# Generate CA private key
if (($step -eq 1) -or ($step -eq "CA") -or ($step -eq "ALL"))
{
    & $openssl genrsa -out $caKey 4096
}

# Generate CA certificate
if (($step -eq 2) -or ($step -eq "CA") -or ($step -eq "ALL"))
{
    & $openssl req -x509 -new -nodes -days 365 -sha256 -key $caKey -out $caCert -subj "$certSubj" -addext "$certExt"
}

# Generate CSR for CA
if (($step -eq 3) -or ($step -eq "CA") -or ($step -eq "ALL"))
{
    #& $openssl req -newkey rsa:4096 -keyout $caprivatekey -out cert.csr -sha256 -days 365 -nodes -subj "$certSubj" -addext "$certExt"
}

# Generate Server Private Key
if (($step -eq 4) -or ($step -eq "SR") -or ($step -eq "ALL"))
{
    & $openssl genrsa -out $serverKey 4096
}

# Generate Server CSR
if (($step -eq 5) -or ($step -eq "SR") -or ($step -eq "ALL"))
{
    & $openssl req -new -key $serverKey -out $serverCsr -sha256 -subj "$serverSubj" -addext "$serverExt"
}

# Generate Server SSL Certificate along with certificates serial
if (($step -eq 6) -or ($step -eq "SR") -or ($step -eq "ALL"))
{
    & $openssl req -x509 -days 365 -sha256 -CA $caCert -CAkey $caKey -in $serverCsr -out $serverCert -addext $serverExt -copy_extensions copyall
}

# Generate Server PFX certificate
if (($step -eq 7) -or ($step -eq "SR") -or ($step -eq "ALL"))
{
    write-host "Generating PFX certificate: you will be prompted to enter a password and confirm it"
    & $openssl pkcs12 -export -in $serverCert -inkey $serverKey -out $serverPfx
    # -certfile root?
}

Running through this script I now have

  • ca.crt, ca.key, ca.srl
  • service01.key, service01.crt, service01.pfx
  • service02.key, service02.crt, service02.pfx

the launchSettings.json for docker is now

    "Docker": {
      "commandName": "Docker",
      "launchBrowser": true,
      "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/",
      "environmentVariables": {
        "ASPNETCORE_URLS": "https://+:443;http://+:80",
        "ASPNETCORE_Kestrel__Certificates__Default__Password": "password",
        "ASPNETCORE_Kestrel__Certificates__Default__Path": "/app/service01.pfx"
      },
      "publishAllPorts": true,
      "useSSL": true,
      "httpPort": 52080,
      "sslPort": 52443,
      "commandLineArgs": "",
      "DockerfileRunArguments": "-h service01"

    }

At this point, service01 is running with the self signed certificate with the root CA included.

so now in web browser https://service01.local comes up with the correct self signed certificate. checking the certificate shows this is valid. same for all three service01.local, service02.loacl, service03.local.

UPDATE 6: still need a way to load the ca cert into the windows container as trusted CA. (lots of linux examples that work, but not windows examples). note that the container does not have powershell or certutil or openssl installed.

2

Answers


  1. Seen this problem days before. You could bypass the certificate validation by config the httpclienthandler when using httpclient in service02.

    var httpClientHandler = new HttpClientHandler();
    httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) =>{return true;};
    builder.Services.AddScoped(sp => new HttpClient(httpClientHandler));
    

    You can also add some validtion if you want

    httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) => {
        if (cert.GetCertHashString() == "A7E2BA992F4F2BE220559B8A5371F05A00A6E750") //FingerPrint of the certificate
        {
            return true;
        }
        return false;
    };
    

    By the way, Below is my working command to run a dockerfile with certificate(.pfx)
    docker run -it -p 22007:22007 -e ASPNETCORE_URLS="https://+:22007" -e ASPNETCORE_HTTPS_PORT=22007 -e ASPNETCORE_Kestrel__Certificates__Default__Password="123" -e ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx -v ${HOME}/.aspnet/https:/https/ myimagename
    Reference build container with https

    —————-update of install cert———-
    Steps of using openssl to create server certificate (pfx):
    1.generate ca.key
    2.generate ca.csr from ca.key
    3.generate ca.crt from ca.csr + ca.key
    4.generate server.key
    5.generate server.csr from server.key
    6.generate server.crt from server.csr + ca.crt + ca.key
    7.generate server.pfx from server.crt + server.key

    In step 6,you will be ask the Common Name(CN), you have to fill in the domain of server01 (or ip, depends on what API URL you are calling in service02).If not same the certificate will be invalid.

    Then use the step7 pfx when run service01 docker to host https.
    My docker file

    FROM mcr.microsoft.com/dotnet/aspnet:6.0   
    COPY / /app/    
    WORKDIR /app    
    EXPOSE 22007/tcp   
    ENV ASPNETCORE_URLS=http://+:22007
    ENTRYPOINT ["dotnet", "/app/dockerBack.dll","--server.urls","http://*/22007"]
    

    My working command (I put the server.pfx in linux home/username/app1/composetest folder):
    docker run -it -p 22007:22007 -e ASPNETCORE_URLS="https://+:22007" -e ASPNETCORE_HTTPS_PORT=22007 -e ASPNETCORE_Kestrel__Certificates__Default__Password="123" -e ASPNETCORE_Kestrel__Certificates__Default__Path="/composetest/server.pfx" -v /home/lighthouse/app1/composetest:/composetest imagename

    Then in service02, you could install the certifiate by adding "copy" "run" at begining in dockerfile. (I put the ca.crt in the same folder of docker file)

    FROM mcr.microsoft.com/dotnet/aspnet:6.0 
    COPY ca.crt /usr/local/share/ca-certificates/ca.crt
    RUN chmod 644 /usr/local/share/ca-certificates/ca.crt && update-ca-certificates
    COPY / /app/    
    WORKDIR /app  
    EXPOSE 22008/tcp
    ENV ASPNETCORE_ENVIRONMENT=Development
    ENV ASPNETCORE_URLS=http://+:22008
    ENTRYPOINT ["dotnet", "/app/docker-front.dll","--server.urls","http://*/22008"]
    

    Then there will be no SSL error in my fetching https api test.(Needn’t bypass validation)

    Login or Signup to reply.
  2. Disabling certificate validation is never an answer. For development/staging environments you can create and trust a self-signed certificate. The .NET SKD has the dotnet dev-certs tool that allows creating and trusting development certificates.

    The article Hosting ASP.NET Core images with Docker over HTTPS shows how you can include and use your dev certificate inside Docker. The development certificate is mounted as a file on the container and the location and password are passed as environment variables.

    You can create a new dev certificate with dotnet dev-certs, save it to a specific location and protect it with a password. In the following command line, <CREDENTIAL_PLACEHOLDER> is the password

    dotnet dev-certs https -ep %USERPROFILE%.aspnethttpsaspnetapp.pfx -p <CREDENTIAL_PLACEHOLDER>
    dotnet dev-certs https --trust
    

    After that, you can create your container using the new certificate and password

    docker pull mcr.microsoft.com/dotnet/samples:aspnetapp
    
    docker run --rm -it -p 8000:80 -p 8001:443 -e ASPNETCORE_URLS="https://+;http://+" -e ASPNETCORE_HTTPS_PORT=8001 -e ASPNETCORE_Kestrel__Certificates__Default__Password="<CREDENTIAL_PLACEHOLDER>" -e ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx -v %USERPROFILE%.aspnethttps:/https/ mcr.microsoft.com/dotnet/samples:aspnetapp
    

    The important parameters are :

    -e ASPNETCORE_Kestrel__Certificates__Default__Password="<CREDENTIAL_PLACEHOLDER>" 
    -e ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx 
    -v %USERPROFILE%.aspnethttps:/https/
    
    • -e ASPNETCORE_Kestrel__Certificates__Default__Password="<CREDENTIAL_PLACEHOLDER>" passes the password to the container’s ASPNETCORE_Kestrel__Certificates__Default__Password environment variable
    • -e ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx stores the inner location of the certificate in an environment variable
    • v %USERPROFILE%.aspnethttps:/https/ mounts the folder where the certificate was created (%USERPROFILE%.aspnethttps) to the inner path /https/
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search