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
Seen this problem days before. You could bypass the certificate validation by config the httpclienthandler when using httpclient in service02.
You can also add some validtion if you want
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
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)
Then there will be no SSL error in my fetching https api test.(Needn’t bypass validation)
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 passwordAfter that, you can create your container using the new certificate and password
The important parameters are :
-e ASPNETCORE_Kestrel__Certificates__Default__Password="<CREDENTIAL_PLACEHOLDER>"
passes the password to the container’sASPNETCORE_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 variablev %USERPROFILE%.aspnethttps:/https/
mounts the folder where the certificate was created (%USERPROFILE%.aspnethttps
) to the inner path/https/