0

I'm working on a project that uses IdentityServer to pass tokens to Ocelot gateway and its micro services. It is working locally but when I want to configure the same setup in a docker container I'm stuck with trusting my self signed certificate. My containers are linux containers and are generated with a docker file per project. These different projects are added together with a docker compose file.

All the containers are running in the same domain and now I want to access my micro services over the gateway with a token that is given by the IdentityServer and all this over https.

This is my docker override file:

version: '3.4'

services:
   abiidentity.web:
     environment:
       ASPNETCORE_ENVIRONMENT: docker
       ASPNETCORE_URLS: https://+;http://+
       Kestrel__Certificates__Default__Password: development
       Kestrel__Certificates__Default__Path: "/root/.aspnet/https/aspnetapp-identity-server.pfx"
     ports:
       - "45570:443"
       - "45571:80"
     volumes:
       - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https
       - type: bind
         source: "${APPDATA}\\ASP.NET\\Https\\aspnetapp-root-cert.cer"
         target: "/root/.aspnet/https/aspnetapp-root-cert.cer"
     depends_on:
       - idendity-db
       - abiweb.apigateway
  abiweb.HRM:
     environment:
       ASPNETCORE_ENVIRONMENT: docker
       ASPNETCORE_URLS: https://+;http://+
       Kestrel__Certificates__Default__Password: development
       Kestrel__Certificates__Default__Path: "/root/.aspnet/https/aspnetapp-web-api.pfx"
     ports:
       - "45591:443"
       - "45590:80"
     volumes:
       - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https
       - type: bind
         source: "${APPDATA}/ASP.NET/Https/aspnetapp-root-cert.cer"
         target: /root/.aspnet/https/aspnetapp-root-cert.cer
   abiweb.apigateway:
     environment:
       ASPNETCORE_ENVIRONMENT: docker
       ASPNETCORE_URLS: https://+;http://+
       Kestrel__Certificates__Default__Password: development
       Kestrel__Certificates__Default__Path: "/root/.aspnet/https/aspnetapp-gateway.pfx"
     ports:
       - "44351:443"
       - "44350:80"
     volumes:
       - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https
       - type: bind
         source: "${APPDATA}/ASP.NET/Https/aspnetapp-root-cert.cer"
         target: /root/.aspnet/https/aspnetapp-root-cert.cer

I have generated the certificates with following script:

# Source: https://stackoverflow.com/a/62060315
# Generate self-signed certificate to be used by IdentityServer.
# When using localhost - API cannot see the IdentityServer from within the docker-compose'd network.
# You have to run this script as Administrator (open Powershell by right click -> Run as Administrator).

$ErrorActionPreference = "Stop"

$rootCN = "IdentityServerDockerDemoRootCert"
$identityServerCNs = "abiidentity.web", "localhost"
$webApiCNs = "abiweb.hrm", "localhost"
$gatewayCns = "abiweb.apigateway", "localhost"

$alreadyExistingCertsRoot = Get-ChildItem -Path Cert:\LocalMachine\My -Recurse | Where-Object {$_.Subject -eq "CN=$rootCN"}
$alreadyExistingCertsIdentityServer = Get-ChildItem -Path Cert:\LocalMachine\My -Recurse | Where-Object {$_.Subject -eq ("CN={0}" -f $identityServerCNs[0])}
$alreadyExistingCertsApi = Get-ChildItem -Path Cert:\LocalMachine\My -Recurse | Where-Object {$_.Subject -eq ("CN={0}" -f $webApiCNs[0])}
$alreadyExistingCertsGateway = Get-ChildItem -Path Cert:\LocalMachine\My -Recurse | Where-Object {$_.Subject -eq ("CN={0}" -f $gatewayCns[0])}

if ($alreadyExistingCertsRoot.Count -eq 1) {
    Write-Output "Skipping creating Root CA certificate as it already exists."
    $testRootCA = [Microsoft.CertificateServices.Commands.Certificate] $alreadyExistingCertsRoot[0]
} else {
    $testRootCA = New-SelfSignedCertificate -Subject $rootCN -KeyUsageProperty Sign -KeyUsage CertSign -CertStoreLocation Cert:\LocalMachine\My
}

if ($alreadyExistingCertsIdentityServer.Count -eq 1) {
    Write-Output "Skipping creating Identity Server certificate as it already exists."
    $identityServerCert = [Microsoft.CertificateServices.Commands.Certificate] $alreadyExistingCertsIdentityServer[0]
} else {
    # Create a SAN cert for both identity-server and localhost.
    $identityServerCert = New-SelfSignedCertificate -DnsName $identityServerCNs -Signer $testRootCA -CertStoreLocation Cert:\LocalMachine\My
}

if ($alreadyExistingCertsApi.Count -eq 1) {
    Write-Output "Skipping creating API certificate as it already exists."
    $webApiCert = [Microsoft.CertificateServices.Commands.Certificate] $alreadyExistingCertsApi[0]
} else {
    # Create a SAN cert for both web-api and localhost.
    $webApiCert = New-SelfSignedCertificate -DnsName $webApiCNs -Signer $testRootCA -CertStoreLocation Cert:\LocalMachine\My
}

if ($alreadyExistingCertsGateway.Count -eq 1) {
    Write-Output "Skipping creating API certificate as it already exists."
    $webApiCert = [Microsoft.CertificateServices.Commands.Certificate] $alreadyExistingCertsGateway[0]
} else {
    # Create a SAN cert for both web-api and localhost.
    $webApiCert = New-SelfSignedCertificate -DnsName $gatewayCns -Signer $testRootCA -CertStoreLocation Cert:\LocalMachine\My
}

# Export it for docker container to pick up later.
$password = ConvertTo-SecureString -String "xxx" -Force -AsPlainText

$rootCertPathPfx = "C:/Users/jvdw/AppData/Roaming/ASP.NET/Https"
$identityServerCertPath = "C:/Users/jvdw/AppData/Roaming/ASP.NET/Https"
$webApiCertPath = "C:/Users/jvdw/AppData/Roaming/ASP.NET/Https"

[System.IO.Directory]::CreateDirectory($rootCertPathPfx) | Out-Null
[System.IO.Directory]::CreateDirectory($identityServerCertPath) | Out-Null
[System.IO.Directory]::CreateDirectory($webApiCertPath) | Out-Null

Export-PfxCertificate -Cert $testRootCA -FilePath "$rootCertPathPfx/aspnetapp-root-cert.pfx" -Password $password | Out-Null
Export-PfxCertificate -Cert $identityServerCert -FilePath "$identityServerCertPath/aspnetapp-identity-server.pfx" -Password $password | Out-Null
Export-PfxCertificate -Cert $webApiCert -FilePath "$webApiCertPath/aspnetapp-web-api.pfx" -Password $password | Out-Null
Export-PfxCertificate -Cert $webApiCert -FilePath "$rootCertPathPfx/aspnetapp-gateway.pfx" -Password $password | Out-Null

# Export .cer to be converted to .crt to be trusted within the Docker container.
$rootCertPathCer = "C:/Users/jvdw/AppData/Roaming/ASP.NET/Https/aspnetapp-root-cert.cer"
Export-Certificate -Cert $testRootCA -FilePath $rootCertPathCer -Type CERT | Out-Null

# Trust it on your host machine.
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store "Root","LocalMachine"
$store.Open("ReadWrite")

$rootCertAlreadyTrusted = ($store.Certificates | Where-Object {$_.Subject -eq "CN=$rootCN"} | Measure-Object).Count -eq 1

if ($rootCertAlreadyTrusted -eq $false) {
    Write-Output "Adding the root CA certificate to the trust store."
    $store.Add($testRootCA)
}

$store.Close()

After executing the script all my pfx files are generated and I then checked if these files are available inside my docker container instances and that is the case.

But when I do a 'wget https://aibiidentity.web' in my terminal of the container instances I get following response:

root@1e3233925091:/app# wget https://abiidentity.web
--2023-03-06 19:30:39--  https://abiidentity.web/
Resolving abiidentity.web (abiidentity.web)... 172.21.0.11
Connecting to abiidentity.web (abiidentity.web)|172.21.0.11|:443... connected.
ERROR: The certificate of 'abiidentity.web' is not trusted.
ERROR: The certificate of 'abiidentity.web' doesn't have a known issuer.

When I check if my root-certificate is trusted I get an ok:

echo | openssl verify /usr/local/share/ca-certificates/aspnetapp-root-cert.crt 
/usr/local/share/ca-certificates/aspnetapp-root-cert.crt: OK

Can somebody tell me what I'm doing wrong? I also followed this blog post: https://mjarosie.github.io/dev/2020/09/24/running-identityserver4-on-docker-with-https.html

Jurgen Vandw
  • 631
  • 8
  • 27

1 Answers1

0

For a linux container to trust your certificate, you need to add a new step on your Dockerfile that copies your certificate to a path like /usr/local/share/ca-certificates/

COPY path/to/your/crt /usr/local/share/ca-certificates/

and then runs update-ca-certificates

RUN sudo update-ca-certificates

Debian based distros, if you're using a RHEL based distro, you can accomplish this but with different commands.

this way the generated image will have your cert on its CA store and hence trust any connection you do to an https endpoint that uses the certificate.

In other words, you need to install the certificate on your host machine to connect to a TLS endpoint exposed by your service. But if that service running on the container also needs to connect to other TLS endpoints exposed by your containers, you need that container to also have the certificate installed, just like your host machine.

You can also create a new Dockerfile and use your original image as base to create a specific docker image with the certificate, that you can use on your docker-compsoe, instead of the original service image.

Pablo Recalde
  • 3,334
  • 1
  • 22
  • 47