JSS headless SSR proxy “unable to verify the first certificate”

When developing a new JSS solution I was looking to have a play locally in headless mode. Unfortunately I hit an issue after standing up a newly minted Sitecore 10 SXA/JSS instance in docker and using it as the apiHost running the node-headless-ssr-proxy sample app locally. After setting up the appropriate values for the proxy and ensuring the react app was in place, I was getting the following error for all SSR layout service requests.

[HPM] Proxy created: /  -> https://xp0cm.localhost
server listening on port 3001!
FetchError: request to https://xp0cm.localhost/sitecore/api/jss/dictionary/jss-sandbox/en?sc_apikey=%7BXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX%7D failed, reason: unable to verify the first certificate
    at ClientRequest.<anonymous> (C:\repos\sandbox\node-proxy\node_modules\node-fetch\lib\index.js:1444:11)
    at ClientRequest.emit (events.js:315:20)
    at TLSSocket.socketErrorListener (_http_client.js:426:9)
    at TLSSocket.emit (events.js:315:20)
    at emitErrorNT (internal/streams/destroy.js:92:8)
    at emitErrorAndCloseNT (internal/streams/destroy.js:60:3)
    at processTicksAndRejections (internal/process/task_queues.js:84:21) {
  type: 'system',
  errno: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
  code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'
}

As a Node n00b, after a little google-foo I determined that the node app did not trust the root (or intermediate) certificate served up by the Sitecore instance’ layout service. But IS trusted when hitting Sitecore or the layout service directly in a browser or via Powershell 🤔.

Cert chain in browser. NB: the Root cert

By default the v10 containers use https (via the Traefik reverse proxy) with certificates generated and signed by a root CA created by the mkcert tool. The provided docker install scripts do all of the cert handling, relying on the mkcert root CA to be in the windows trusted certificate store, which happens when the mkcert is installed.

The mkcert root CA under the “Trusted Root Certification Authorities”

Unfortunately, Node does not use the Windows certificate stores and manages it’s own static list of trusted CAs. So Node (doing the SSR calls to layout service) will not trust the mkcert root CA unless it is explicitly told to do so.

NB: You can disable SSL certificate validation of proxy traffic by setting the SITECORE_PROXY_SECURE environment variable to false, however assuming you do want end to end validated TLS (even in dev) you have a couple of options.

This can be achieved by using an npm package to fetch trusted certificates from the Windows store and make them available to Node OR by creating a file containing the “extra certs” and making them available to Node via an environment variable. Both approaches have pros and cons, but the npm package is quick and easy for local dev environments on Windows.

The win-ca npm package method

The win-ca npm package will fetch all trusted certs from the Windows trusted CA store and make them available to Node. While this solution is really is specific to Windows environments, win-ca won’t choke up on other platforms.

It’s super easy to get up and running for a dev environment:

#install the npm package
npm install win-ca --save

#require it's usage in the node-proxy app. EG. Near the top of config.js
require('win-ca');

That’s it. Restart the application (npm start) and the mkcert CA should now be trusted.

The NODE_EXTRA_CA_CERTS method

While slightly more difficult to implement, the following methods offer a bit more control depending on your requirements (and OS…it’s possible the same issue is hit on Linux if self signed certs were used). Node uses the NODE_EXTRA_CA_CERTS environment variable to add extra certificates you would like Node to trust. It goes without saying that this should be used sparingly and really only in local or pre-production environments. eg: in Powershell:

#adds the NODE_EXTRA_CA_CERTS environment variable for the session
$env:NODE_EXTRA_CA_CERTS="certs/ExtraCA.cer" 

But first, we need to get the certs in a useable format. In this particular case, we only want to add the root CA that mkcert installs to ensure the entire cert chain is trusted, however it’s the same process if it’s a different cert you need Node to trust.

Firstly, Node expects to be able to read the “extra certs” file in PEM format. This can easily be done as a once off via the Windows cert manager GUI or Powershell. By default mkcert installs it’s root CA in the current user cert store.

Export via Windows cert manager GUI

Search “Manage user certificates” from the start menu or control panel to launch the cert manager GUI.

Right click on the mkcert certificate. Click All Tasks > Export

Select Base-64 encoded X.509 (.CER)

Save the file and note the location.

Export via Powershell

Running something like the following will export the mkcert CA in the appropriate format to ./certs/ExtraCA.cer .

$path = "./certs/"
$temp = "$path/in.cer"
Get-Item Cert:\CurrentUser\Root\* | Where-Object { $_.Subject -like "*mkcert*" } | Export-Certificate Type CERT FilePath $temp Verbose
certutil encode $temp "$path/ExtraCA.cer"
Remove-Item $temp
view raw getCA.ps1 hosted with ❤ by GitHub

Loading the exported certificate into env vars

Now that you have the extra certs ready for loading, just set the environment variable. For local dev environments, it’s easiest to set it for the current user, then start the application.

#Powershell (User) - Good for local dev:
#adds the NODE_EXTRA_CA_CERTS environment variable for the user (restart your shell before starting the app)
[System.Environment]::SetEnvironmentVariable('NODE_EXTRA_CA_CERTS','certs/ExtraCA.cer',[System.EnvironmentVariableTarget]::User)

##Or if needed you could:

#Powershell (session) before starting the app:
#adds the NODE_EXTRA_CA_CERTS environment variable for the session
$env:NODE_EXTRA_CA_CERTS="certs/ExtraCA.cer"

#NPM (windows)
#add a script to package.json that loads the var at start time:
...
"scripts": {
    "start": "node index.js",
    "start:dev": "SET NODE_EXTRA_CA_CERTS=certs/ExtraCA.cer && npm start"
  },
...

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s