ASP.NET core and integrated windows authentication in nanoserver container

 

 

Below is overview of steps required to use integrated Windows Authentication in ASP.NET core application inside nanoserver container.

Completion of this instructions will allow you to run nanoserver container in windows AD environment and authenticate users based on their Windows desktop credentials or externally by providing Windows username/password to AD environment. This setup also RBAC access using Windows Groups as explained below.

Entire project with source code located on following github repo (https://github.com/artisticcheese/ContainerWindowsAuth)

High level overview of steps required to complete this scenario

  1. Complete first 7 steps in following article (Enabling integrated Windows Authentication in windows docker container)
  2. Build docker container based off github repo above
  3. Launch docker container in AD environment with GMSA account (step 8 in Enabling integrated Windows Authentication in windows docker container)

Detailed instructions

 

Completion steps 1-7 from following article Enabling integrated Windows Authentication in windows docker container will prepare your AD environment to host windows container with integrated windows authentication.

Add following snippet to startup.cs of your .NET core project which will enabled integrated windows authentication to be used in .NET core

Add .UseIISIntegration() to your  BuildWebHost method of Program.cs

HomeController has 4 methods below. Windows requires authentication and allows only members Domain Admins, Users allow only Domain Users group, LocalUsers allow any account passed by Windows authentication and Anonymousis allowing to be accessed without authentication

So accessing URL on your running container at http://container/home/windows will require authentication and http://container/home/anonymous is allowed to be accessed without authentication.

Multistage build is used to build .NET core project and put in resulting container along with all neccessary prerequisites (IIS and ASP.NET core). For detailed instructions you can read in this article (Using multistage docker build to create IIS + ASP.NET Core image + nanoserver).

Dockerfile for entire project is below. It shall be pretty self explanatory.

Building final image puts together container image consisting of 3 intermediate images used to:

  • Build ASP.NET core project
  • Copy ASP.NET executables to final image
  • Install ASP.NET IIS module to servercore image and copy it over to final image

Once image is built you can deploy to production container host which was prepared earlier in step 1 above.

Launch docker image with following parameters

docker run -d -h containerhost --security-opt "credentialspec=file://win.json" -p 88:80 artisticcheese/winauth:nano-iis

Parameters specify which GMSA account is being used to authenticate to AD and location of JSON file with details.

Accessing URL http://containerhost:88/Home/Windows on container host from domain joined client will automatically log you with current user credentials with no prompt like below. While accessing http://containerhost:88/Home/Anonymous will allow you to access it without username/password.

Accessing with user account which is not part of Domain Admins will return HTTP 403 on /home/windows as expected but will succeed on /home/users

See screenshots below. If you don’t want to automatically log in with default credentials you can access page via IP address which will prevent IE/Chrome from using them. http://192.168.1.235:88/Home/Windows, please note that your authentication switches to NTLM instead of Kerberos authentication in such case.

To check local user hardcoded into image you can go to /Home/LocalUsers and user ContainerAdminand password A123456!

vmconnect_2017-09-13_11-48-14

Advertisements

Using multistage docker build to create IIS + ASP.NET Core image + nanoserver

Kestrel webserver (server which serves ASP.NET core exe) does not support integrated Windows authentication and requires external proxy server to handle this. One of the those proxy servers is IIS. Problem is that there is no docker image provided by Microsoft which have IIS + ASP.NET core + nanoserver. Further complicating things are the fact that there is no easy and straightforward way to install IIS on nanoserver image which has ASP.NET core, neither it’s easy to install ASP.NET core on IIS image build on nanoserver. There is multitude articles and script which are doing little parts here and there but none provide complete solution in part due to requirement to have ASP.NET core module to be first installed on full server core image before DLLs can be extracted for use in nanoserver IIS image.

Thanks to 17.05 docker options to build multistage image this entire pretty easily accomplished with single docker file.

Dockerfile below is using 2 middleware images to pull relevant files into final image which contains ASP.NET core + IIS on top of nanoserver.

Image microsoft/aspnetcore:2.0.0-nanoserver  is used for extraction of .NET core installation.

Image microsoft\iis based off WindowsServerCore is used to install .NET core webhosting package (EXE which upon execution places required ASP.NET core module and schema into middleware container) and later extract 2 necessary files (module itself and schema file) to be used in final image.

Final image is built from 2 middleware images above with some additional configuration necessary for ASP.NET core module to work in IIS and path to DOTNET core is added to environment Path variable.

Enabling integrated Windows Authentication in windows docker container

Walk through below will enable integrated Windows Authentication for windows docker container in Active Directory environment.

Overview of steps are below

  1. Create Global Security group Container Hosts  in Active Directory
  2. Add container host servers to group which is allowed to decrypt password GMSA account
  3. Reboot container host so computer account have proper group membership
  4. Create GMSA account in Active Directory
  5. Install GMSA account on container host
  6. Add SPN record to GMSA account
  7. Generate credentialspecs file to be passed to docker daemon during container startup
  8. Launch docker container with proper parameters

Detailed implementation steps are below

Create Global Security group Container Hosts  in Active Directory

vmconnect_2017-09-09_14-35-19

Add computer account for container host to Container Hosts group

vmconnect_2017-09-09_14-52-30

Reboot container host computer

This step is required so computer account will have proper AD group associated with it

Create GMSA account in Active Directory

On domain controller execute

Install GMSA account on container host and test it

Output shall not contain any errors and will look like below

Path : 
Online : True
RestartNeeded : False

DistinguishedName : CN=containerhost,CN=Managed Service Accounts,DC=ad,DC=local
Enabled : True
Name : containerhost
ObjectClass : msDS-GroupManagedServiceAccount
ObjectGUID : 12941984-5fd3-4095-96f9-cbd96902eb36
SamAccountName : containerhost$
SID : S-1-5-21-3914853822-719528391-929614657-1606
UserPrincipalName :

True

Add SPN record to GMSA account

This step is required if for kerberos authentication to work and for automatic login in Chrome/IE. Without it authentication will still work but will always prompt for username/password since it will fallback to NTLM.

Launch adsiedit.msc on domain controller and add SPN of HTTP/containerhost1.ad.local to GMSA account

vmconnect_2017-09-09_15-05-23

Generate credentialspecs file to be passed to docker daemon during container startup

Execute powershell below to generate your GMSA configuration file which will be used by docker to enable Windows Authentication

 

Launch docker container with proper parameters

Launching docker with parameters specifying GMSA account name as well as credential specs file

docker run -d -p 8080:80 -h containerhost --security-opt "credentialspec=file://win.json" artisticcheese/winauth:servercore

You can use my image artisticcheese/winauth:servercore to test if this solution works. Image has just plain vanilla IIS with windows authentication enabled and default page showing which account are you logged on with

Now if you access your container host on port 8080 on any domain joined machine you shall be automatically authenticated. If you try to access your containerhost via IP address then you will be prompted for username/password since IE/chrome will assume you are accessing external computer and hence will not automatically log you on.

vmconnect_2017-09-09_19-46-09

Integrating docker release management in TFS with webtests

TFS express 2017 provides free and fully integrated environment for continious integration and release management for docker windows containers. Post below only focuses on release management for docker containers via TFS and webtests produced by Visual Studio Enterprise edition.

My current setup of TFS build produces following artifacts available for release management.

  • docker-stack.yml

This file presented below provides information about build version of container image and in imagetag and additional information for running environment

  • tests folder with 3 files
    • local.testsettings
      • This file provides information which server webtest have to be running against

    • webtest1.webtest
      • This file provides actual steps which is tested

  • runtest.ps1
    • This file which performs 2 functions: waits for swarm manager to bring latest version of image up and then run webtests tests on those.

Release pipeline itself contains of 3 steps for each environment. Screenshot below for staging environment.

chrome_2017-07-27_15-13-27

First step is pushing newly built image to staging docker swarm. This step is using built-in Docker command tasks in TFS and passing information about swarm location and registry connection via built in TFS properties along with docker-stack.yml file above.

chrome_2017-07-27_15-17-31

Second step is powershell script named runtest.ps1above. Variables  are passed to script to specify which server needs to be checked against as well current version of new build version of docker image

chrome_2017-07-27_15-20-09

Last step is publishing results of those tests

chrome_2017-07-27_15-21-52

Below is screenshot what failure in webtest looks like. Along with failure also attached webtestresult file which you can open in Visual Studio to inspect details of what has failed.

chrome_2017-07-27_15-25-25

Please note to enable test run you need to have mstest.exe installed on TFS agent as part of Visual Studio Enterprise 2017 installation. You can use Evaluation Version to install it. You need to start Visual Studio at least once under user which agent is running under to fix the issue below (if your agent runs as System service then you need to run Visual Studio at least one a System). Test will fail otherwise with File extension specified .webtest is not a valid test extension.

Another thing to note is that there is currently a bug in TFS 2017 U2 which does not properly pull results form TRX file and hence additional logic was added to powershell script to put results in correct location to Publish Test Results tag can find results.

 

Comparing windows containers CPU perfomance vs alternatives

When presenting containers solution to wider audience question of perfomance invariably comes to place (the same sort of discussion everybody had 10 years ago during virtualization craze). I decided to do unscientific test of running Windows containers vs alternatives. Specifically windows containers are compared against running the same application on physical hardware, inside Hyper-V VM on the same hardware as well as windows containers in process and hypervisolation modes.

You can check results by yourself as image is posted at artisticcheese\iis on docker hub.

Code which is used for testing is designed to return PI number calculates to certain number which is very CPU intensive operation. In my specific run I was calculating to 8000s place with total of 100 requests in multithreaded client.

Client code is below, which is powershell code relying on runspaces via PSParallel module


#requires -Modules PSParallel
Measure-command {1..100 | Invoke-Parallel -ScriptBlock
{Invoke-WebRequest http://localhost/service.svc/pi/8000 -UseBasicParsing}}

Below are results of running this tests against 4 different environments

Test environment Hyper-V Hardware Process Isolation HyperV isolation
96.47 90.12 90.77 91.4
97.12 90.13 90.86 90.24
97.87 89.27 90.49 91.3

Conclusion may be surprising or may be not but running Windows containers introduce virtually no overhead from CPU point of view on application perfomance.

Replacing ServiceMonitor.exe with IIS error log events in windows IIS container

ServiceMonitor.exe is used as default ENTRYPOINT entry official Microsoft IIS build. It does not seem to be doing much except for checking if service W3SVC is running. There is multitude of issues with this EXE based on github (https://github.com/Microsoft/iis-docker/issues). Bearing in mind very limited functionality that this EXE provides I decided it to replace with something more useful.
Starting with IIS 8.5 it’s possible to output IIS logs not only to log files but also to ETW events. You can consume those inside eventlog name called Microsoft-Windows-IIS-Logging/Logs which shall be enabled to receive those events. Steps below will allow to provide output of all errors being logged on your webserver to docker logs instead of default ENTRYPOINT of IIS image which does not provide any valuable information.
I also decided to start my images now in servercore instead of IIS since latter only installs WindowsFeature Web-Server and creates ENTRYPOINT for ServiceMonitor.exe. Neither of which is really necessary anyway.
To accomplish this 2 things would need to be changed in base Microsoft image.

  1. Enable IIS to log to ETW (in addition or insted of file logging)
  2. Enable log called Microsoft-IIS-Logging/logs

Step number 1 is accomplished by executing following powershell

Import-module WebAdministration
$splat = @{
    pspath = "MACHINE/WEBROOT/APPHOST"
    filter = "system.applicationHost/sites/siteDefaults/logFile"
    name =  "logTargetW3C"
    value = "File,ETW"
}
Set-WebConfigurationProperty @splat

Step 2 is below

$IISOpsLog = Get-WinEvent -ListLog Microsoft-IIS-Logging/logs
$IISOpsLog.IsEnabled = "true"
$IISOpsLog.SaveChanges()

Both entries are made inside website_config.ps1 file in artifacts directory.

This setup will output ETW events to Microsoft-IIS-Logging/logs which will be repeatadly read in powershell script called entrypoint.ps1 below

$VerbosePreference = "ignore"
$sleep = 5
while ($true)
{
    $datediff = (New-TimeSpan -Seconds $sleep).TotalMilliseconds
    $filter = "*/System/TimeCreated[timediff(@SystemTime) <= $datediff] and *[EventData/Data[@Name='sc-status'] >'400']"
    Get-WinEvent -MaxEvents 10 -FilterXPath $filter -ProviderName "Microsoft-Windows-IIS-Logging" -ErrorAction SilentlyContinue | 
    Select-Object @{Name = "time"; e = {$_.Properties[2].value}}, @{Name = "VERB"; e = {$_.Properties[8].value}}, 
    @{Name = "ClientIP"; e = {$_.Properties[3].value}}, @{Name = "URI"; e = {$_.Properties[9].value}}, 
    @{Name = "Query"; e = {$_.Properties[10].value}}, @{Name = "Status"; e = {$_.Properties[11].value}}, 
    @{Name = "host"; e = {$_.Properties[21].value}} | Format-Table
    Start-Sleep $sleep
}

I restrict number of events returned by query at source based on time since last request as well as only for specific eventcodes which identifies Web Server errors (status codes > 400)

The last step is to put this script into ENTRYPOINT in DOCKERFILE

ENTRYPOINT powershell.exe C:\startup\entrypoint.ps1

Entire code base along with additional files is available on Github page (https://github.com/artisticcheese/IISadmin)
Image on dockerhub is (https://hub.docker.com/r/artisticcheese/iis-admin/)

You can test functionality below

docker run -it artisticcheese/iis-admin

Issue request to non-existent file to local container

invoke-webrequest http://172.30.163.51/asda

You will see output in stdout of container

time     VERB ClientIP     URI   Query Status host         
----     ---- --------     ---   ----- ------ ----         
18:28:08 GET  172.30.160.1 /asda -        404 172.30.163.51

Using pure powershell to generate TLS certificates for Docker daemon running on Windows

Steps below will allow you to create necessary PKI infrastructure to secure you docker daemons with no requirement to download any external tools. Snippets below are not part of complete script but tidbits which you can use to procure both CA, server certificate as well as client certificate and reuse pieces for issuing additional certificates down the road.

Docker daemon requires 3 files on server to for secure TLS connection:

  • tlscacert which is Base64 encoded public key of CA certificate
  • tlscert which is Base64 encoded public key of server certificate
  • tlskey which Base64 encoded private key of server certificate

Docker daemon will list those keys in file called daemon.json under  $env:programdata\docker\config

Example of that file is below

{
"group": "Network Service",
"graph": "E:\\images",
"tlscacert": "C:\\ProgramData\\docker\\certs.d\\rootCA.cer",
"tlskey": "C:\\ProgramData\\docker\\certs.d\\privateKey.cer",
"hosts": [
"tcp://0.0.0.0:2376",
"npipe://"
],
"tlscert": "C:\\ProgramData\\docker\\certs.d\\serverCert.cer",
"tlsverify": true
}

Similar files will be required on client to connect to server, difference is certificate which will be used to connect which will have different EKU (Enhanced Key Usage) specified

  • tlscacert which is Base64 encoded public key of CA certificate
  • tlscert which is Base64 encoded public key of client certificate
  • tlskey which is Base64 encoded private key of client certificate
Docker client will use syntax below to connect to TLS secured docker endpoint
& docker --tlsverify --tlscacert=c:\test\rootca.cer --tlscert=c:\test\clientPublicKey.cer --tlskey=c:\test\clientPrivateKey.cer -H=tcp://containerhost1:2376 version

Creating CA certificate

Snippet below creates CA certificate and exports it’s public key to c:\test\rootCA.cer. Private key stays in your Windows Certificate Store and is exportable for your backup purpouses and reissuing new server and client certificates later. The only changeable parameter which you can modify for your environment is Subject.

        $splat = @{
        type = "Custom" ;
        KeyExportPolicy = "Exportable";
        Subject = "CN=Docker TLS Root";
        CertStoreLocation = "Cert:\CurrentUser\My";
        HashAlgorithm = "sha256";
        KeyLength = 4096;
        KeyUsage = @("CertSign", "CRLSign");
        TextExtension = @("2.5.29.19 ={critical} {text}ca=1")
    }
    $rootCert = New-SelfSignedCertificate @splat

After CA certificate is generated we need to export it’s public key to a file, the only changeable part here is Path

 $splat = @{
Path = "c:\test\rootCA.cer";
        Value = "-----BEGIN CERTIFICATE-----`n" + [System.Convert]::ToBase64String($rootCert.RawData, [System.Base64FormattingOptions]::InsertLineBreaks) + "`n-----END CERTIFICATE-----";
        Encoding = "ASCII";
    }
    Set-Content @splat

Creating Server Certificate to secure TLS on container host

Code similar to generation of CA certificate with few notable changes, that is we provide which certificate is used to sign it as well as type of certificate, we export key after cert is generated. Changeable parameters are DNSName and Path

    $splat = @{
        CertStoreLocation = "Cert:\CurrentUser\My";
        DnsName = "swarmmanager1", "localhost", "containerhost1";
        Signer = $rootCert ;
        KeyExportPolicy = "Exportable";
        Provider = "Microsoft Enhanced Cryptographic Provider v1.0";
        Type = "SSLServerAuthentication";
        HashAlgorithm = "sha256";
        TextExtension = @("2.5.29.37= {text}1.3.6.1.5.5.7.3.1");
        KeyLength = 4096;
    }
    $serverCert = New-SelfSignedCertificate @splat
    $splat = @{
       Path = "c:\test\serverCert.cer";
       Value = "-----BEGIN CERTIFICATE-----`n" + [System.Convert]::ToBase64String($serverCert.RawData, [System.Base64FormattingOptions]::InsertLineBreaks) + "`n-----END CERTIFICATE-----";
       Encoding = "Ascii"
}
   Set-Content @splat

Exporting private key for server certificate to a file

Last step for TLS connectivity for docker host to export private key to Base64 encoded file which has been pretty difficult with off the shelf powershell/.NET framework untill version 4.6 which provided method to export that key. Implementation is below

    $privateKeyFromCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($serverCert)
    $splat = @{
        Path = "c:\test\privateKey.cer";
        Value = ("-----BEGIN RSA PRIVATE KEY-----`n" + [System.Convert]::ToBase64String($privateKeyFromCert.Key.Export([System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob), [System.Base64FormattingOptions]::InsertLineBreaks) + "`n-----END RSA PRIVATE KEY-----");
        Encoding = "Ascii";
    }
    Set-Content @splat

Creating client certificate

Code below performs similar tasks in relevant to client certificate like server certificate tasks above

    $splat = @{
        CertStoreLocation = "Cert:\CurrentUser\My";
        Subject = "CN=clientCert";
        Signer = $rootCert ;
        KeyExportPolicy = "Exportable";
        Provider = "Microsoft Enhanced Cryptographic Provider v1.0";
        TextExtension = @("2.5.29.37= {text}1.3.6.1.5.5.7.3.2") ;
        HashAlgorithm = "sha256";
        KeyLength = 4096;
    }
    $clientCert = New-SelfSignedCertificate  @splat
    $splat = @{
        Path = "c:\test\clientPublicKey.cer" ;
        Value = ("-----BEGIN CERTIFICATE-----`n" + [System.Convert]::ToBase64String($clientCert.RawData, [System.Base64FormattingOptions]::InsertLineBreaks) + "`n-----END CERTIFICATE-----");
        Encoding = "Ascii";
    }
    Set-Content  @splat
    $clientprivateKeyFromCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($clientCert)
    $splat = @{
        Path = "c:\test\clientPrivateKey.cer";
        Value = ("-----BEGIN RSA PRIVATE KEY-----`n" + [System.Convert]::ToBase64String($clientprivateKeyFromCert.Key.Export([System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob), [System.Base64FormattingOptions]::InsertLineBreaks) + "`n-----END RSA PRIVATE KEY-----");
        Encoding = "Ascii";
    }
    Set-Content  @splat

If everything worked correctly you are supposed to see 3 certificates on your machine listed below, which are CA certificate, server certificate and client certificate, you also shall have private key for each of those (indicated by key image).

mmc_2017-06-10_16-10-12

You shall also have 5 files created in c:\test folder like below

&amp;amp;amp;amp;amp;amp;nbsp;Directory: C:\test

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        6/10/2017   4:09 PM           3314 clientPrivateKey.cer
-a----        6/10/2017   4:09 PM           1858 clientPublicKey.cer
-a----        6/10/2017   4:09 PM           3310 privateKey.cer
-a----        6/10/2017   4:09 PM           1812 rootCA.cer
-a----        6/10/2017   4:09 PM           1936 serverCert.cer

Add your CA root certificate to your trusted root certificate authorities in certmgr.msc. Make sure you copy and paste and not move CA root certificate since if you move you will not be able to sign any more keys. This is not not a requirement but will allow you to use native Windows tools in working with certificates instead of relying on file based store like openSSL does. For example you will be able to use HTTPS to call docker REST API both in browser and via Invoke-WebRequest

Deploy server certificate to docker container host

  • Open file named daemon.json under  $env:programdata\docker\config and paste lines in snippet below into it.
    "tlscacert":  "C:\\ProgramData\\docker\\certs.d\\rootCA.cer",
    "tlskey":  "C:\\ProgramData\\docker\\certs.d\\privateKey.cer",
    "hosts":  [
                  "tcp://0.0.0.0:2376",
                  "npipe://"
              ],
    "tlscert":  "C:\\ProgramData\\docker\\certs.d\\serverCert.cer",
    "tlsverify":  true
  • Copy files rootCA.cer, privateKey.cer, serverCert.cer to $env:programdata\docker\certs.d
  • Restart docker service Restart-Service docker

At this point you shall not be able to connect to daemon via HTTPS without providing a valid certificate. Try following  Invoke-WebRequest https://containerhost1:2376, it shall fail complaining that SSL client certificate is required for connection

The request was aborted: Could not create SSL/TLS secure channel.
At line:1 char:1
+ Invoke-WebRequest https://containerhost1:2376
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

Attach client certificate to request by first finding thumbprint of your client cert in certificates manager and then use tab completion to iterate to it in get-item cert:\CurrentUser\My\, assign it to variable which you will attach to REST request $cert = get-item Cert:\CurrentUser\My\350A62B64152D9B85673E902A1F1C2CB6766598E

Issue the same request as above and it will succeed returning information about available images on remote system

PS >(Invoke-WebRequest https://containerhost1:2376/images/json -Certificate $cert -UseBasicParsing).Content | convertfrom-json

Containers : -1
Created : 1494389426
Id : sha256:242b8694ed621610a27746e0075c95e87f1a239e1800a4ea55e753010a49d9d5
Labels :
ParentId :
RepoDigests : {stefanscherer/dockertls-windows@sha256:5fe358a57cb31f18d2d148b0481898d530a5547c4d5d6f9ce5e0334ed8d3de19}
RepoTags : {stefanscherer/dockertls-windows:latest}
SharedSize : -1
Size : 1049291645
VirtualSize : 1049291645

One last thing is to try to use docker CLI to query the same information.

PS C:\admin> docker --tlsverify --tlscacert=c:\test\rootca.cer --tlscert=c:\test\clientPublicKey.cer --tlskey=c:\test\clientPrivateKey.c
er -H=tcp://containerhost1:2376 images

time="2017-06-10T16:46:14-05:00" level=info msg="Unable to use system certificate pool: crypto/x509: system root pool is not available on Windows"
REPOSITORY                        TAG                 IMAGE ID            CREATED             SIZE
stefanscherer/dockertls-windows   latest              242b8694ed62        4 weeks ago         1.05 GB

Full script is below

$ErrorActionPreference = "Stop"
if ([int](Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full"  -Name Release).Release -lt 393295) {
    throw "Your version of .NET framework is not supported for this script, needs at least 4.6+"
}
function GenerateCerts {
    $splat = @{
        type = "Custom" ;
        KeyExportPolicy = "Exportable";
        Subject = "CN=Docker TLS Root";
        CertStoreLocation = "Cert:\CurrentUser\My";
        HashAlgorithm = "sha256";
        KeyLength = 4096;
        KeyUsage = @("CertSign", "CRLSign");
        TextExtension = @("2.5.29.19 ={critical} {text}ca=1")
    }
    $rootCert = New-SelfSignedCertificate @splat
    $splat = @{
        Path = "c:\test\rootCA.cer";
        Value = "-----BEGIN CERTIFICATE-----`n" + [System.Convert]::ToBase64String($rootCert.RawData, [System.Base64FormattingOptions]::InsertLineBreaks) + "`n-----END CERTIFICATE-----";
        Encoding = "ASCII";
    }
    Set-Content @splat
    $splat = @{
        CertStoreLocation = "Cert:\CurrentUser\My";
        DnsName = "swarmmanager1", "localhost", "containerhost1";
        Signer = $rootCert ;
        KeyExportPolicy = "Exportable";
        Provider = "Microsoft Enhanced Cryptographic Provider v1.0";
        Type = "SSLServerAuthentication";
        HashAlgorithm = "sha256";
        TextExtension = @("2.5.29.37= {text}1.3.6.1.5.5.7.3.1");
        KeyLength = 4096;
    }
    $serverCert = New-SelfSignedCertificate @splat
    $splat = @{
        Path = "c:\test\serverCert.cer";
        Value = "-----BEGIN CERTIFICATE-----`n" + [System.Convert]::ToBase64String($serverCert.RawData, [System.Base64FormattingOptions]::InsertLineBreaks) + "`n-----END CERTIFICATE-----";
        Encoding = "Ascii"
    }
    Set-Content @splat

    $privateKeyFromCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($serverCert)
    $splat = @{
        Path = "c:\test\privateKey.cer";
        Value = ("-----BEGIN RSA PRIVATE KEY-----`n" + [System.Convert]::ToBase64String($privateKeyFromCert.Key.Export([System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob), [System.Base64FormattingOptions]::InsertLineBreaks) + "`n-----END RSA PRIVATE KEY-----");
        Encoding = "Ascii";
    }
    Set-Content @splat

    $splat = @{
        CertStoreLocation = "Cert:\CurrentUser\My";
        Subject = "CN=clientCert";
        Signer = $rootCert ;
        KeyExportPolicy = "Exportable";
        Provider = "Microsoft Enhanced Cryptographic Provider v1.0";
        TextExtension = @("2.5.29.37= {text}1.3.6.1.5.5.7.3.2") ;
        HashAlgorithm = "sha256";
        KeyLength = 4096;
    }
    $clientCert = New-SelfSignedCertificate  @splat
    $splat = @{
        Path = "c:\test\clientPublicKey.cer" ;
        Value = ("-----BEGIN CERTIFICATE-----`n" + [System.Convert]::ToBase64String($clientCert.RawData, [System.Base64FormattingOptions]::InsertLineBreaks) + "`n-----END CERTIFICATE-----");
        Encoding = "Ascii";
    }
    Set-Content  @splat
    $clientprivateKeyFromCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($clientCert)
    $splat = @{
        Path = "c:\test\clientPrivateKey.cer";
        Value = ("-----BEGIN RSA PRIVATE KEY-----`n" + [System.Convert]::ToBase64String($clientprivateKeyFromCert.Key.Export([System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob), [System.Base64FormattingOptions]::InsertLineBreaks) + "`n-----END RSA PRIVATE KEY-----");
        Encoding = "Ascii";
    }
    Set-Content  @splat
}
GenerateCerts

& docker --tlsverify --tlscacert=c:\test\rootca.cer --tlscert=c:\test\clientPublicKey.cer --tlskey=c:\test\clientPrivateKey.cer -H=tcp://containerhost1:2376 images