Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use V3 CosmosClient with Emulator while ignoring SSL errors? #1232

Closed
olivermue opened this issue Feb 27, 2020 · 28 comments
Closed

How to use V3 CosmosClient with Emulator while ignoring SSL errors? #1232

olivermue opened this issue Feb 27, 2020 · 28 comments

Comments

@olivermue
Copy link

Describe the bug
Impossible to connect CosmosClient to Emulator while trying to ignore SSL certificates.

I already searched this repo and the web to find some information about how to ignore the SSL error if we try to connect to the emulator. All I could find was some hints on how to solve these problems on V2 DocumentClient (like #42), but nothing for V3 CosmosClient.

I dig through the public interface of the class, their options, etc. and the only maybe promising I could find was the Collection<RequestHandler> CustomHandlers. So I wrote my own handler (see below), added it to the options
new CosmosClient(EndPoint, Key, new CosmosClientOptions { CustomHandlers = { new IgnoringSSLErrorsRequestHandler() } });
and guess what, it won't be called (so I also don't know if the implementation is correct or does really work).

Expected behavior
Some code example on how it is possible to instantiate an CosmosClient that will ignore SSL errors.

Actual behavior
Nothing is available to work around this problem, except importing the certificate to the windows certificate store, but we need some solution in pure code.

Environment summary
SDK Version: 3.6.0
OS Version: Windows

Additional context

internal class IgnoringSSLErrorsRequestHandler : RequestHandler
{
    private readonly HttpClient _httpClient;

    public IgnoringSSLErrorsRequestHandler()
    {
        var messageHandler = new HttpClientHandler { ServerCertificateCustomValidationCallback = (req, cert, chain, errors) => true };
        _httpClient = new HttpClient(messageHandler);
    }

    public override async Task<ResponseMessage> SendAsync(RequestMessage request, CancellationToken cancellationToken)
    {
        var httpRequest = new HttpRequestMessage
        {
            Method = request.Method,
            RequestUri = request.RequestUri,
        };

        foreach (var headerKey in request.Headers.AllKeys())
        {
            httpRequest.Headers.Add(headerKey, request.Headers[headerKey]);
        }

        foreach (var kvp in request.Properties)
        {
            httpRequest.Properties.Add(kvp);
        }

        var response = await _httpClient.SendAsync(httpRequest, cancellationToken);

        return new ResponseMessage(response.StatusCode, request)
        {
            Content = await response.Content.ReadAsStreamAsync()
        };
    }
}
@j82w
Copy link
Contributor

j82w commented Feb 27, 2020

The emulator has documented steps on how to setup the certificate on linux and Mac environments.

@olivermue
Copy link
Author

I'd really like to NOT import a certificate somewhere. I'd like to use the emulator locally without validating the certificate in my test code. That should really be possible, especially for testing.

@j82w
Copy link
Contributor

j82w commented Feb 27, 2020

It was decided that importing the certificate somewhere is the safer option that brings the testing environment closer to actual production. It's very easy to let a client option to get passed up through production causing a big security issue. There is no easy way to detect it is the emulator and detecting if it is production has it's own security issues.

@ealsur
Copy link
Member

ealsur commented Mar 3, 2020

@olivermue This is a similar scenario to when you run an HTTPS application in a local server with a self-signed certificate.
If you want to connect to that HTTPS application from other machine or instance, you need to trust that certificate by adding it to your list of trusted ones because it is a self-signed certificate.

The Emulator has its own certificate, that when you run locally in the same machine, it is automatically trusted because it is the same machine. This works out of the box.

If you want to access the Emulator from a different machine or environment (if you use Docker or a VM), you need to trust the certificate in order to establish an HTTPS connection, this is a basic HTTPS usage scenario for any secure application.

As @j82w said, adding programmatic ways to disable SSL only leads to production security issues when that code is accidentally pushed to production. The "hassle" of correctly trusting the certificate in a testing environment is rather better than the potential security breach of pushing code to production, don't you think?

@erjok
Copy link

erjok commented Apr 19, 2020

Why not add an option to run the emulator with HTTP connection?
Similar to asp.net core where you have both HTTP and HTTPS. I wonder actually why HTTPS is used for the emulator. Someone was going to run the emulator on prod environment?

I might be wrong, but having HTTP option will solve all problems with accessing emulator from other machines or containers. And also this option will allow use exactly same code for dev and prod environments.

@whung1
Copy link

whung1 commented Apr 28, 2020

To echo @erjok sentiment, I think we (as MSFT) should give developers the benefit of the doubt of being smart enough to not disable SSL for production environment and provide a way to ignore SSL errors.

Currently I'm dealing with the issue of trying to get Remote Development containers work with Cosmos Db Emulator. This is because my MSFT team develops in Windows but wants to move towards containers.

We previously supported overriding the SSL in v2's DocumentClient as seen in the documentation here.

However, using v2 does not work for me due to implementation of v2 not supporting host.docker.internal URIs as seen by the issue here, which is the only clean way to connect from my container to the host machine that hosts Cosmos Db Emulator because MSFT hasn't been able to move the emulator into a linux container since June 2018 because of constant delays.

I also understand/appreciate intended sentiment on installing the local development certificate because we consider this the "right way". However, even installing the cert locally as @j82w suggested does not work as .NET Core relies on OpenSSL and OpenSSL says the generated cert's public key for CosmosDbEmulator (RSA 1024 bit), is too weak. I manually downgraded the openssl configuration, but alas, the alternative subject name obviously wouldn't match, since I'll never be trying to hit the host machine through localhost from my Linux container.

So just to trust the cert locally in my container, am I supposed to generate my own cert that's valid for Cosmos Db Emulator with DNS of host.docker.internal, export the pfx on windows, convert the pfx to a crt on my linux container, trust the crt on my Linux container, downgrade openssl config SECLEVEL in my container on bootup, and then distribute that to the rest of my team because this is the "right way"? I did get the SSL somewhat working via the GenCert argument provided by Cosmos Db Emulator, but I'm dreading scripting the first half and getting the second half working with the mounted volume during Dockerfile.

I'm still having issues getting the actual client to work because of an "Endpoint not reachable. Refresh cache and retry" warning because the v3 client for some reason resolves https://host.docker.internal:8081 to https://127.0.0.1:8081/ which it can't hit because obviously 127.0.0.1:8081 does not exist in the container.
image
image

I will probably give up this approach, and figure out a way to grab my host machine's external ip from my container (which is such a niche need I have no idea where to start) as per this reply and abandon v3 cosmosclient for documentclient.

Meanwhile, I would prefer if there was an alternative solution that is a bit cleaner and developer friendly so I didn't have to jump through so many hoops to get to this conclusion.

@j82w
Copy link
Contributor

j82w commented Apr 28, 2020

@whung1 does this PR #1441 unblock your scenario?

@ealsur
Copy link
Member

ealsur commented Apr 28, 2020

@whung1 Are you working with the latest version of the Emulator? When we released this updated doc: https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator#running-on-mac-or-linux, I went through and tested this on Docker, WSL2, and on Mac, and in all cases, we were able to connect to the Emulator because we updated the cert that the Emulator produces to contain the required information OpenSSL was asking for.

@whung1
Copy link

whung1 commented Apr 28, 2020

Sorry, I didn't mean to hijack this thread for my troubleshooting.

Asking a user to connect to the emulator via ipconfig and then replacing environment values inside the container is not an ideal solution if I'm producing a remote development container that will work with the whole team, as ip can change per user and it's a manual step, and it's hard to grab the external ip of the host machine from the container during container's build in the Dockerfile. Unfortunately this might be the only solution left available.

Using the latest version, I am still generating a RSA public key of 1024 bits, which I noted above, will cause openssl issues based on default configurations. I tried doing multiple clean installs from a previous version so not sure if that's a factor.
image

It seems like the PR linked by @j82w will resolve this current issue we're on for bypassing SSL since we are able to provide the http client with a http client handler to ignore SSL via the injected http client factory.

I'm not sure if my (host.docker.internal resolution) issue that I gave screenshots of above isn't exactly addressed since I'm not sure what is transforming host.docker.internal incorrectly to 127.0.0.1.
I previously used Gremlin .Net in the past for CosmosDb Graph and host.docker.internal resolution worked fine. You can see that if I ping host.docker.internal in this screenshot, it also does resolution properly. (Obviously 192.168.65.2 won't work with cert).
image
image

I'm also not sure if my issue is worth the investment for the team with the timeline for the docker linux container. I can open a separate github issue on connecting via host.docker.internal if that's preferred, where it can be resolved either through changing the client or having the emulator containerized in linux.

@ealsur
Copy link
Member

ealsur commented Apr 28, 2020

The issue with host.docker.internal might be related to that domain not matching localhost which is the one used in the Certificate.

V3 SDK has Direct mode by default, which uses a different set of ports than Gateway mode, did you try using Gateway mode?

In all honesty, I used docker and was able to connect to the Emulator running on the Windows instance after importing the certificate as explained in the guide on the linux docker instance. We did the same thing on a Mac, running the Emulator inside a Windows VM. The missing piece was the certificate was missing the KeyUsage attribute, which OpenSSL requires.

Are you starting the Emulator with Network access enabled? (a. .\Microsoft.Azure.Cosmos.Emulator.exe /AllowNetworkAccess /Key=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==)

What you need to make sure is that the machine with Windows and the Emulator, has the ports open correctly so an external process can connect to those ports, this is not particular to the Emulator, if I wanted an external instance to connect to a SQL Server running in a Windows machine, I would need to make sure that the ports are open and there is no Firewall blocking anything.

@whung1
Copy link

whung1 commented Apr 29, 2020

Thanks for the help @ealsur

I was able to get host.docker.internal working with /AllowNetworkAccess. This makes it so we don't have to do ipconfig on host machine to manually replace any containerized env variable endpoints for development containers.

  • This does not work on the v2 client because v2 client is not able to resolve host.docker.internal (gives Name or Service not known) where as v3 does
  • Direct/Gateway mode both work on v3
  • I'm still unsure on why Gremlin endpoint with Cosmos Db Emulator did not need /AllowNetworkAccess to work with host.docker.internal based on my experience there before

Steps:

  1. Use .\Microsoft.Azure.Cosmos.Emulator.exe /GenCert=host.docker.internal to generate and install a cert that allows host.docker.internal to be trusted.
  2. Export the PFX to a directory that will be bind mounted in the container.
    • Example script that outputs to directory based on defaults of Dotnet Core's default bootstrap container for Visual Studio Code's Remote Development.
$pfxPath=$env:USERPROFILE + '\.aspnet\https\' + 'cosmosdbemulator.pfx'
$pfxPwd=ConvertTo-SecureString 'SecurePwdGoesHere' -asplaintext -force
Get-ChildItem -path Cert:\LocalMachine\My -Recurse | where {$_.FriendlyName -eq 'DocumentDbEmulatorCertificate'} | Export-PfxCertificate -Filepath $pfxPath -Password $pfxPwd
  1. Mount the directory in docker for the PFX export if not done already
  2. Follow the existing linux import certificate documentation steps.
    • This needs to be done every time after your container is built, since we cannot access the pfx in the mounted directory during the Dockerfile to run these scripts.
    • This is my example script that I am using postCreateCommand in Remote Development's devcontainer.json. I do not know an alternative method to run scripts after the container is built with just docker. I'll give my example script below.
#!/bin/bash

# Save current working directory
PWD=`pwd`
pushd $PWD

# Find and move to the location of this script
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $DIR

COSMOS_DB_EMULATOR_PFX="/home/vscode/.aspnet/https/cosmosdbemulator.pfx" # Mounted pfx file
COSMOS_DB_EMULATOR_PFX_PASSWORD="SecurePwdGoesHere"
CERT_TO_TRUST="cosmosdbemulator.crt"

# Generate .crt file if pfx exists and .crt does not already exist
if [ -f "$COSMOS_DB_EMULATOR_PFX" ] && ! [ -f "$CERT_TO_TRUST" ]; then
    openssl pkcs12 -in $COSMOS_DB_EMULATOR_PFX -clcerts -nokeys -out cosmosdbemulator.crt -passin pass:$COSMOS_DB_EMULATOR_PFX_PASSWORD;
fi

# Trust Cert (will be located in /etc/ssl/certs/)
if [ -f "$CERT_TO_TRUST" ]; then
    cp $CERT_TO_TRUST /usr/local/share/ca-certificates/
    update-ca-certificates;
fi

# Restore working directory
popd
  1. Run the emulator on your host machine via .\Microsoft.Azure.Cosmos.Emulator.exe /FailOnSslCertificateNameMismatch /allownetworkaccess /Key=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==
  2. Connect using v3 CosmosClient with URI of https://host.docker.internal:8081

As you can see, installing the certificate is not trivial (steps 1-4, where step 4 is especially inconvenient). Thus hopefully as per this ticket, we can ignore SSL errors in the future and then steps 5-6 are trivial to get a nice development container working irregardless of where the emulator is hosted (on host machine, on another linux container within the same docker network).

Again, I think the PR #1441 linked by j82w worked on by ealsur might be able give this workaround by adding clienthandler to the httpclient from the httpfactory example.

@joshystuart
Copy link

Another vote for an "insecure" dev mode. I have spent far too long trying to get this to work in docker across several languages.

@DaleyKD
Copy link

DaleyKD commented May 19, 2020

It sounds like I need to carefully analyze @whung1 's answer.

Our dev scenario:

  • Windows 10 Host
  • Many microservices in docker Linux containers
  • Azure Cosmos DB Emulator in the Windows 10 Host
  • Microservice in Linux container needs to access the Cosmos DB Emulator via https://host.docker.internal:8081

The SSL issue is stopping us because of localhost vs host.docker.internal in the SAN/CN/subject/whatever.

@j82w
Copy link
Contributor

j82w commented May 19, 2020

@DaleyKD can't you now use the HttpClientFactory released in 3.9.0 to disable the SSL validation?

@DaleyKD
Copy link

DaleyKD commented May 20, 2020

@j82w - Man, that would BE AWESOME.

However, when I try to use the HttpClientFactory func in CosmosClientOptions, it appears to never hit the breakpoint of trying to get an HttpClient.

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("cosmosHttpClient").ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()
    {
        ClientCertificateOptions = ClientCertificateOption.Manual,
        ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, cetChain, policyErrors) => true
    });

    services.AddSingleton(sp =>
    {
        var httpClientFactory = sp.GetService<IHttpClientFactory>();
        var cosmosClientOptions = new CosmosClientOptions()
        {
            HttpClientFactory = () => httpClientFactory.CreateClient("cosmosHttpClient"),
            ConnectionMode = ConnectionMode.Gateway
        };

        return new CosmosClient("https://host.docker.internal:8081", "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==", cosmosClientOptions);
    });

    services.AddRazorPages();
}

Index.cshtml.cs

public class IndexModel : PageModel
{
    private readonly CosmosClient _cosmosClient;

    public IndexModel(CosmosClient cosmosClient)
    {
        _cosmosClient = cosmosClient;
    }

    public async Task OnGetAsync()
    {
        var database = await _cosmosClient.CreateDatabaseIfNotExistsAsync("MyDatabaseName");
    }
}

When we get to .CreateDatabaseIfNotExistsAsync, the breakpoint on HttpClientFactory is never hit, nor is the one in my HttpClientHandler for the SSL verification.

This is making me feel like I'm obviously missing something.

EDIT: Forgot to mention that as soon as we try to call .CreateDatabaseIfNotExistsAsync, we get the good ol' AuthenticationException: The remote certificate is invalid according to the validation procedure. I see nothing in the callstack that refers to GatewayStoreModel, but I do see:

Microsoft.Azure.Cosmos.DocumentClient.InitializeGatewayConfigurationReaderAsync()
Microsoft.Azure.Cosmos.DocumentClient.GetInitializationTaskAsync(IStoreClientFactory storeClientFactory)
Microsoft.Azure.Cosmos.DocumentClient.EnsureValidClientAsync()

which is before a new GatewayStoreModel is instantiated. It's like the DocumentClient doesn't want to use the HttpClientFactory option.

@ealsur
Copy link
Member

ealsur commented May 20, 2020

Some digging into this usecase got into #1548

@DaleyKD
Copy link

DaleyKD commented May 20, 2020

@ealsur That is some VERY impressive quick turnaround.

Before we implement this in our system (we're currently in a spike for learning how to use Cosmos), can you think of any other classes in the SDK where we may still need to do some tweaks to utilize the HttpClientFactory?

I'm happy to run some preliminary tests to see if we can use this after this PR is accepted.

@FVilevski
Copy link

I totally agree with the developers above, just because it is the "Right Way" doesn't mean that we should do "Rocket Science" to perform a simple stupid connection with the emulator that is ONLY NEEDED for dev mode. This is very frustrating and people that are working with Docker and CosmosDB cannot do simple development at the moment of writing this post.

Either write the correct docs pages on Microsoft website with a step-by-step guide or give a possibility to disable SSL when developing with docker.

BTW: I have also opened a ticket for this issue #1551

@whung1
Copy link

whung1 commented May 22, 2020

@DaleyKD

Sorry for not seeing your replies. I have the same dev scenario as you. I already unblocked my team on this and have it working for multiple members but am waiting for the cleaner solution still as well.

While the Cosmos team does the good work to circumvent SSL, my steps should unblock you. The point of the /GenCert flag is to generate the cert with host.docker.internal as part of the SAN. Then the Fail on ssl mismatch flag makes sure the emulator doesn't generate a new one that doesnt have host.docker.internal as part of the cert SAN. I'm providing the exact scripts I use below.

I would recommend manually cleaning the certs if running through my steps above again as you might have some leftover certs generated from before, or running my new script below that does that for you. When you start the cosmos emulator via the command I supplied, you can click the browser and verify its using the cert you expect by hitting the lock button and checking the cert generated and is being used as expected
image
image

Here is my setup script that I use for my team to both install and output the cert in the current directory.
NOTE: I now output directly in source directory instead of depending on mounted directory like I did before because the bash script will not be able to access the mounted directory during dockerfile build. This was not an issue with Visual Studio Code containers since they can run commands after the dockerfile is built, but is an issue for more vanilla docker setups that I needed to support.
image

Stop-Process -Name "Microsoft.Azure.Cosmos.*"

# Cosmos DB Emulator Installation
$confirmation = Read-Host "Do you want to install Cosmos Database Emulator? (y/n)"
if ($confirmation -eq 'y') {
    Write-Host "Installing Azure Cosmos Db Emulator"
    $installer = "$PSScriptRoot/cosmosEmulatorInstaller.msi"
    curl https://aka.ms/cosmosdb-emulator -O $installer
    Start-Process -Wait -FilePath msiexec -ArgumentList /i, $installer
    Remove-Item $installer
}

# Custom Certificate Generation to work with host.docker.internal
$certFriendlyName="DocumentDbEmulatorCertificate"
$pfxPath=$PSScriptRoot + '\' + 'cosmosdbemulator.pfx'
# Remove any existing cosmos emulator certs, then generate a new one
if (Test-Path $pfxPath) {
    Write-Host "Removing existing PFX cert"
    Remove-Item -Path $pfxPath
}
Get-ChildItem -path Cert:\LocalMachine\My -Recurse | where {$_.FriendlyName -eq $certFriendlyName} | Remove-Item # Note that this doesn't remove in cert manager for some reason
$cosmosDb = Get-ChildItem HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall | % { Get-ItemProperty $_.PsPath } | where {$_.DisplayName -eq 'Azure Cosmos DB Emulator'} | Select InstallLocation
cd $cosmosDb.InstallLocation
.\Microsoft.Azure.Cosmos.Emulator.exe /GenCert=host.docker.internal
# Start sleep to allow cert generation to propagate before continuing
Start-Sleep -s 5

# Export cert to script directory
$pfxPwd=ConvertTo-SecureString 'SecurePwdGoesHere' -asplaintext -force
Get-ChildItem -path Cert:\LocalMachine\My -Recurse | where {$_.FriendlyName -eq $certFriendlyName} | Export-PfxCertificate -Filepath $pfxPath -Password $pfxPwd

Then I slightly modified my bash script in the same folder to run as a step during the dockerfile

#!/bin/bash

# Save current working directory
PWD=`pwd`
pushd $PWD

# Find and move to the location of this script
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $DIR

if [ -n "$1" ]; then
    COSMOS_DB_EMULATOR_PFX=$1
else
    COSMOS_DB_EMULATOR_PFX="./cosmosdbemulator.pfx"
fi
COSMOS_DB_EMULATOR_PFX_PASSWORD="SecurePwdGoesHere"
CERT_TO_TRUST="cosmosdbemulator.crt"

# Generate .crt file if pfx exists
if [ -f "$COSMOS_DB_EMULATOR_PFX" ]; then
    openssl pkcs12 -in $COSMOS_DB_EMULATOR_PFX -clcerts -nokeys -out cosmosdbemulator.crt -passin pass:$COSMOS_DB_EMULATOR_PFX_PASSWORD;
fi

# Trust Cert (will end located in /etc/ssl/certs/ based on *.crt name as a *.pem, e.g. /etc/ssl/certs/cosmosdbemulator.pem for cosmosdbemulator.crt)
if [ -f "$CERT_TO_TRUST" ]; then
    cp $CERT_TO_TRUST /usr/local/share/ca-certificates/
    update-ca-certificates
    rm $CERT_TO_TRUST;
fi

# Restore working directory
popd

Then for Visual Studio Code containers simply add the bash script as part of the postCreateCommand in devcontainer.json. For vanilla dockerfiles add the script to run by copying and running it. Of course pay attention that the scripts folder is same level or below your Dockerfile context.
For Visual Studio Code Remote Development Containers:
image
For Dockerfile:

WORKDIR /scripts
COPY /scripts/ .
RUN ./trust_cosmos_db_emulator_crt.sh

Then the run script is the same, but just a bit nicer to people that don't want to install in default location for some reason and restarts process if it's already running.

Stop-Process -Name "Microsoft.Azure.Cosmos.*"
$cosmosDb = Get-ChildItem HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall | % { Get-ItemProperty $_.PsPath } | where {$_.DisplayName -eq 'Azure Cosmos DB Emulator'} | Select InstallLocation
& "$($cosmosDb.InstallLocation)\Microsoft.Azure.Cosmos.Emulator.exe" /FailOnSslCertificateNameMismatch /AllowNetworkAccess /Key=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==

Then overall steps are the same for the user. Run setup script the first time, then rebuild docker container calling the bash script that has the .pfx file, then make sure cosmos is running via the run script before the service starts up.

I hope that helps meanwhile.

@DaleyKD
Copy link

DaleyKD commented May 26, 2020

How funny! I had started working on my .ps1 (I've got one we use to help manage our local docker images/container), and this is the function I wrote:

function InstallCosmosDbEmulator() {
	$r = Get-WmiObject Win32_Product | Where {$_.IdentifyingNumber -match '{DF0D8EAC-4939-400C-B122-4BC715BF2DB6}' }
	if ($r -eq $null) {
		Invoke-WebRequest 'https://aka.ms/cosmosdb-emulator' -OutFile 'cosmosdb-emulator.msi'
		.\cosmosdb-emulator.msi
	} else {
		Write-Host "Azure Cosmos DB Emulator is already installed." -ForegroundColor Green
	}

	$tmpPath = Join-Path -Path $PSScriptRoot -ChildPath "nginx/certs/DocumentDbEmulatorCertificate.crt"
	if (!(Test-Path $tmpPath)) {
		CosmosDbEmulatorCert
	}

	# Modify the .lnk file (ASSUMES ADMIN ACCESS)
	Push-Location -EA Stop "$env:AllUsersProfile\Microsoft\Windows\Start Menu\Programs\Azure Cosmos DB Emulator"

	$shell = New-Object -COM WScript.Shell
	$shortcut = $shell.CreateShortcut((Get-Location).Path + "\Azure Cosmos DB Emulator.lnk")
	$shortcut.Arguments = "/FailOnSslCertificateNameMismatch /AllowNetworkAccess /Key=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
	$shortcut.Save()

	Pop-Location
}

function CosmosDbEmulatorCert() {
	$cosmosDb = Get-ChildItem HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall | % { Get-ItemProperty $_.PsPath } | where {$_.DisplayName -eq 'Azure Cosmos DB Emulator'} | Select InstallLocation
	Push-Location -EA Stop $cosmosDb.InstallLocation

	# Proper shut down
	$x = Start-Process "Microsoft.Azure.Cosmos.Emulator.exe" -ArgumentList "/GetStatus" -PassThru
	if ($x.ExitCode -ne 2) {
		.\Microsoft.Azure.Cosmos.Emulator.exe /Shutdown
		$process = Get-Process "Microsoft.Azure.Cosmos.Emulator" -ErrorAction SilentlyContinue
		Wait-Process -InputObject $process
	}

	.\Microsoft.Azure.Cosmos.Emulator.exe /GenCert=host.docker.internal
	Start-Sleep -Seconds 5
	Pop-Location

	$tmpPath = Join-Path -Path $PSScriptRoot -ChildPath "nginx/certs"
	if (!(Test-Path $tmpPath)) {
		New-Item -ItemType Directory -Force -Path $tmpPath
	}

	# set certificate password here
	$pfxPassword = ConvertTo-SecureString -String "password" -Force -AsPlainText
	$pfxFilePath = "$($tmpPath)\DocumentDbEmulatorCertificate.pfx"
	$cerFilePath = "$($tmpPath)\DocumentDbEmulatorCertificate.crt"
	
	$cert = Get-ChildItem -Path cert:\LocalMachine\My -Recurse | where {$_.FriendlyName -eq 'DocumentDbEmulatorCertificate'}
	Export-PfxCertificate -Cert $cert -Filepath $pfxFilePath -Password $pfxPassword
	
	Push-Location -EA Stop "$env:ProgramFiles\OpenSSL\bin\"
	.\openssl pkcs12 -in $pfxFilePath -clcerts -nokeys -out $cerFilePath -passin pass:password
	Pop-Location
}

We already had OpenSSL installed for previous reasons.

Then, I was simply going to modify my docker-compose to mount the .crt.

@priyankajayaswal1
Copy link

I also have the same use case as @whung1 and there does exist disableSSLVerification key in other cosmos-db language SDKs. I was able to use the disableSSLVerification for python and it was neat and something which I was looking for C# as well. While searching , I got to know that similar thing exists in azure-cosmos-js as well.
It's way better to have a disableSSLVerification feature added to avoid the hassle of extra automation as suggested above and increase adoption.
Do consider adding disableSSLVerification feature in your coming sprints, because clearly from the thread above, it's not just folks at Microsoft ( @whung1 and me), but others too who are looking forward to it.

@joshystuart
Copy link

How funny! I had started working on my .ps1 (I've got one we use to help manage our local docker images/container), and this is the function I wrote:

I'm guessing these scripts are run within the dockerfile as part of the build?

@DaleyKD
Copy link

DaleyKD commented Jun 1, 2020

How funny! I had started working on my .ps1 (I've got one we use to help manage our local docker images/container), and this is the function I wrote:

I'm guessing these scripts are run within the dockerfile as part of the build?

@joshystuart - For me, I use the .ps1 as part of the development cycle. It has assisted us with managing the docker-compose and Docker containers. It helps:

  • Starts up (or Starts up + Builds) one of 4 different sets of docker-compose
  • Shuts them down
  • Quick option to Prune
  • Quick option to inspect containers/images
  • Quick option to see logs or access bash/shell.

It manages:

  • Dependencies, such as NGINX, SQL, Azurite, Consul, etc.
  • The core set of microservices, including Identity Server and Ocelot gateways
  • The 4 different web sites we have

But, in regards to this topic, it also:

  • Downloads any prereq pieces of software, like Azure Cosmos DB and Open SSL
  • Modifies our hosts file to set some URLs, like "https://myapp.domain.local"
  • Generates SSL scripts needed for NGINX and other containers to allow no SSL cert issues

So, I just added the necessary SSL certs from Cosmos DB to be a part of the "Initial Setup" steps described above.

@joshystuart
Copy link

@DaleyKD thank you so much for your detailed response!

Getting cosmos up and running locally for development, and in CI for our functional testing, has been a real nightmare. This is something that should have taken hours, not days! :(

@DaleyKD
Copy link

DaleyKD commented Jun 2, 2020

Haha. I joined our current cloud product team in October with no knowledge of Docker, Cosmos, Azure, Azure Storage, etc. I cursed Docker for weeks until I wrote that .ps1. I think getting that .ps1 to do what I needed it to do took a full Planning Increment week and some of the next sprint. A big portion of that was learning NGINX and what it took to mirror our Azure Cloud setup.

Once I learned all of this, next thing I know, it's not so bad. And that includes getting this SSL issue for Cosmos DB resolved. All the work (especially SSL certs) is done thru the .ps1 instead of in each docker image.

To be fair, we haven't fully stood it up in local dev yet. We should be getting that working on Monday.

@DaleyKD
Copy link

DaleyKD commented Jun 16, 2020

Additional comment...

Ideally, we'll be using EF Core Cosmos DB provider. However, in version 3.1, it doesn't support IDictionary<string, object>, so I've got a strange hybrid where I use a DbContext, but then insert/update/select using the underlying CosmosClient.

When thinking about how to allow us to get past this SSL issue (without the huge 'create a special cert for Docker, blah blah'), is it possible for you to coordinate with EF Core and find a way to allow us to bypass SSL issues using the DbContext ?

I've fought the emulator a LOT lately, and it's greatly frustrated me, simply because of this local dev issue. We get it to work for a while, but then the emulator decides to regenerate the old style key.

@joshystuart
Copy link

When thinking about how to allow us to get past this SSL issue (without the huge 'create a special cert for Docker, blah blah')

I gave up trying to work around the ssl issue, and I am currently trying to get a custom cosmos docker image to work.

Honestly, this has been the biggest waste of dev time in my entire life. The MS Cosmos team should add a no SSL config option; just like the rest of the tools we use! You dont have to run mongo, mysql, etc etc locally with a cert.... you can if you want to, but it isn't mandatory. That's the part that annoys me most; treating developers with kid gloves!

@j82w
Copy link
Contributor

j82w commented Aug 7, 2020

3.12.0 is released with the necessary changes.

Here are examples on how to configure it:

CosmosClientOptions cosmosClientOptions = new CosmosClientOptions()
{
    HttpClientFactory = () =>
    {
        HttpMessageHandler httpMessageHandler = new HttpClientHandler()
        {
            ServerCertificateCustomValidationCallback = (req, cert, chain, errors) => true
        };
        return new HttpClient(httpMessageHandler);
    },
    ConnectionMode = ConnectionMode.Gateway
};


CosmosClient client = new CosmosClient("https://localhost:8081", 
    "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==", 
    cosmosClientOptions);

Or if you are using a NET Standard 2.1 project:

CosmosClientOptions cosmosClientOptions = new CosmosClientOptions()
{
    HttpClientFactory = () =>
    {
        HttpMessageHandler httpMessageHandler = new HttpClientHandler()
        {
            ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator 
        };
        return new HttpClient(httpMessageHandler);
    },
    ConnectionMode = ConnectionMode.Gateway
};


CosmosClient client = new CosmosClient("https://localhost:8081", 
    "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==", 
    cosmosClientOptions);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants